From 83584949b7d05f16888f3fc358dcdd6c4dfec09d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 15 Aug 2026 11:25:11 +0200 Subject: [PATCH 1/2] feat(protocol): select runtime tool projections --- ...capability-selected-runtime-projections.md | 5 + docs/building/by-layer/L0/schemas.mdx | 55 +++++++++ docs/protocol/calling-an-agent.mdx | 2 +- scripts/build-schemas.cjs | 14 +++ scripts/mcp-schema-projection.cjs | 107 +++++++++++++++++- static/schemas/source/manifest.schema.json | 6 + .../media-buy/list-products-request.json | 1 + .../get-adcp-capabilities-request.json | 1 + tests/mcp-schema-projection.test.cjs | 101 +++++++++++++++++ 9 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 .changeset/add-capability-selected-runtime-projections.md diff --git a/.changeset/add-capability-selected-runtime-projections.md b/.changeset/add-capability-selected-runtime-projections.md new file mode 100644 index 0000000000..2f607c040d --- /dev/null +++ b/.changeset/add-capability-selected-runtime-projections.md @@ -0,0 +1,5 @@ +--- +"adcontextprotocol": minor +--- + +Define deterministic capability-selected runtime tool projections, add optional concise manifest summaries, and keep response schemas available for lazy SDK validation outside model context. diff --git a/docs/building/by-layer/L0/schemas.mdx b/docs/building/by-layer/L0/schemas.mdx index 8f0eebb6a0..a14a0608b6 100644 --- a/docs/building/by-layer/L0/schemas.mdx +++ b/docs/building/by-layer/L0/schemas.mdx @@ -304,6 +304,61 @@ clients combine the model-context inputs with each live MCP tool's concise `name` and `description`; the downloadable model-context manifest is not a description catalog by itself. +#### Capability-selected runtime projection + +AdCP 3.2 hosts MUST derive a live MCP tool surface from the release manifest +and the tools the endpoint can actually dispatch. A runtime projection is a +selection of the generated per-tool bundles, not another schema profile and +not a second hand-maintained tool catalog. + +The deterministic selection algorithm is: + +1. Build `implemented_tools` from the endpoint's dispatch registry. Every name + MUST exist in the canonical release manifest. Do not infer implementation + from documentation, a role profile, or a protocol claim. +2. If the session has no narrower capability scope, select + `implemented_tools`. Otherwise, select the implemented tools whose manifest + `protocol` is enabled, unioned with exact enabled tool names. Convert + `supported_protocols` snake case to manifest kebab case (`media_buy` → + `media-buy`) before comparing. Exact tool claims that are not implemented + are configuration errors; hosts MUST fail closed rather than advertise + them. +3. Treat `protocol` as ownership metadata, not dependency closure. Shared task, + account, or discovery tools are included only when the host adds their exact + names. Selection never pulls in neighboring tools implicitly. +4. Production projections MUST exclude the `compliance` protocol. Deprecated + compatibility facades are included only when the endpoint really implements + and advertises them; deprecation alone is not a runtime filter. +5. Sort selected names lexicographically. For each selected name, emit one MCP + `tools/list` entry containing `name`, the optional manifest `summary` as the + live `description`, and the corresponding self-contained `inputSchema` from + the MCP projection. Do not emit unselected tools or load response schemas + into the model-facing list. +6. Keep the release manifest and response bundles available outside model + context. SDKs validate a direct result through that tool's `response_schema`; + they resolve terminal polling results through `task_result_resolution`. + +The coarse protocol set normally comes from `supported_protocols`. Exact names +come from the active capability blocks (for example `lifecycle_tools`, +`repair_tasks`, and `projection_tasks`) plus shared tools the host exposes for +that session. The implementation registry remains the upper bound in every +case. Unknown protocol or tool names, duplicate selector inputs, and production +attempts to expose compliance tools are errors. + +For caller-side version adaptation, the live discovery result is authoritative. +An SDK calls a current split 3.2 tool when that name is present. If it is absent, +the SDK checks the current tool's `legacy_fallback` manifest entry and may use +the named legacy tool only when that name is live: `direct` is a one-call +translation, `orchestrated` must preserve the current operation's state and +idempotency semantics across the sequence, and `none` is unsupported. SDKs +MUST NOT infer a fallback from a shared protocol classification. + +The checked-in `production`, `media-buy`, and `creative` profiles remain useful +catalogs and role-oriented starting points. They are not mandatory runtime +combinations. The complete canonical projection remains the authority for +documentation, conformance, compatibility analysis, code generation, and lazy +response validation. + AdCP 4.0 will make JSON Schema 2020-12 the canonical source dialect. That major-version migration is where the protocol may selectively use `unevaluatedProperties`, `dependentRequired`, `dependentSchemas`, and other diff --git a/docs/protocol/calling-an-agent.mdx b/docs/protocol/calling-an-agent.mdx index 4ad9a6981c..fa6d5d61df 100644 --- a/docs/protocol/calling-an-agent.mdx +++ b/docs/protocol/calling-an-agent.mdx @@ -15,7 +15,7 @@ The agent-facing version of this content lives at [`skills/call-adcp-agent/SKILL Walk these in order on first contact with any new agent: -1. **Agent card** (A2A) or **`tools/list`** (MCP): returns tool *names*. AdCP MCP servers no longer publish per-tool parameter schemas in `tools/list` — every tool shows `{type: 'object', properties: {}}`. Don't try to infer shape from there. +1. **Agent card** (A2A) or **`tools/list`** (MCP): returns the tools selected for this endpoint or session. An AdCP 3.2 MCP server includes each selected tool's self-contained `inputSchema`; it does not load the complete AdCP catalog or its response schemas into model context. Treat the live names as the authority for what can be called. 2. **[`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities)**: returns supported protocols, AdCP major versions, and feature flags. Tells you *which* tools this agent supports, not how to call them. See [`get_adcp_capabilities`](/docs/protocol/get_adcp_capabilities). 3. **`get_schema(tool_name)`** *(when the agent exposes it — pending standardization, see [#3057](https://github.com/adcontextprotocol/adcp/issues/3057))*: returns the JSON Schema for a specific tool's request/response. 4. **Bundled schemas** (offline, authoritative): every published AdCP version ships JSON Schemas for every tool, signed via Sigstore. The path differs by SDK — the spec repo source uses `dist/schemas//bundled/`, `@adcp/sdk` puts them at `schemas/cache//bundled/` after `npm run sync-schemas`, Python and Go SDKs use their own conventions. Don't hardcode a path; let the SDK's loader find them. Once located, each schema lives at `/-{request,response}.json`. diff --git a/scripts/build-schemas.cjs b/scripts/build-schemas.cjs index e799839db9..89a31f1321 100644 --- a/scripts/build-schemas.cjs +++ b/scripts/build-schemas.cjs @@ -1180,6 +1180,18 @@ function discoverTools(sourceDir) { const requestSchema = JSON.parse(fs.readFileSync(requestPath, 'utf8')); const responseName = `${toolBase}-response.json`; const responseSchema = `${protocol}/${responseName}`; + const summary = requestSchema['x-tool-summary']; + if (summary !== undefined && ( + typeof summary !== 'string' + || summary.length === 0 + || summary.length > 240 + || summary !== summary.trim() + || /[\r\n]/.test(summary) + )) { + throw new Error( + `Manifest generation: ${protocol}/${f.name} has invalid x-tool-summary metadata` + ); + } const legacyFallback = requestSchema['x-legacy-fallback']; if (legacyFallback !== undefined) { const keys = legacyFallback && typeof legacyFallback === 'object' && !Array.isArray(legacyFallback) @@ -1223,6 +1235,7 @@ function discoverTools(sourceDir) { name: toolName, protocol, mutating, + ...(summary ? { summary } : {}), operation_family: requestSchema['x-operation-family'] || toolName, idempotency_requirement: Array.isArray(requestSchema.required) && requestSchema.required.includes('idempotency_key') ? 'required' @@ -1305,6 +1318,7 @@ function buildManifest(sourceDir, urlVersion, semverVersion, repoRoot) { toolsObj[t.name] = { protocol: t.protocol, mutating: t.mutating, + ...(t.summary ? { summary: t.summary } : {}), operation_family: t.operation_family, idempotency_requirement: t.idempotency_requirement, request_schema: t.request_schema, diff --git a/scripts/mcp-schema-projection.cjs b/scripts/mcp-schema-projection.cjs index 55014fe09f..fe7372113d 100644 --- a/scripts/mcp-schema-projection.cjs +++ b/scripts/mcp-schema-projection.cjs @@ -617,6 +617,106 @@ function writeJson(filename, value) { fs.writeFileSync(filename, `${JSON.stringify(value, null, 2)}\n`, 'utf8'); } +function uniqueStringSet(values, label, { required = false } = {}) { + if (values === undefined && !required) return new Set(); + if (!Array.isArray(values) || values.some(value => typeof value !== 'string' || value.length === 0)) { + throw new Error(`${label} must be an array of non-empty strings`); + } + const result = new Set(values); + if (result.size !== values.length) throw new Error(`${label} must not contain duplicates`); + return result; +} + +/** + * Select the exact runtime tool surface from a release manifest. + * + * implementedTools is the host's dispatch registry and is always the hard + * upper bound. capabilityProtocols is a coarse protocol-family scope; + * capabilityTools adds exact cross-protocol tools and per-session exceptions. + * Protocol classification is ownership metadata, not dependency closure, so + * this function never adds related tools implicitly. + */ +function selectRuntimeToolNames(manifest, { + implementedTools, + capabilityProtocols, + capabilityTools, + production = true, +} = {}) { + if (!manifest || typeof manifest !== 'object' || !manifest.tools || typeof manifest.tools !== 'object') { + throw new Error('manifest must contain a tools object'); + } + + const implemented = uniqueStringSet(implementedTools, 'implementedTools', { required: true }); + const protocolInputs = uniqueStringSet(capabilityProtocols, 'capabilityProtocols'); + const protocols = new Set([...protocolInputs].map(protocol => protocol.replaceAll('_', '-'))); + if (protocols.size !== protocolInputs.size) { + throw new Error('capabilityProtocols must not contain equivalent snake_case and kebab-case values'); + } + const exactTools = uniqueStringSet(capabilityTools, 'capabilityTools'); + const knownTools = new Set(Object.keys(manifest.tools)); + const knownProtocols = new Set(Object.values(manifest.tools).map(tool => tool.protocol)); + + for (const toolName of implemented) { + if (!knownTools.has(toolName)) throw new Error(`implementedTools names unknown tool ${toolName}`); + } + for (const protocol of protocols) { + if (!knownProtocols.has(protocol)) throw new Error(`capabilityProtocols names unknown protocol ${protocol}`); + if (production && protocol === 'compliance') { + throw new Error('production runtime projections cannot select the compliance protocol'); + } + } + for (const toolName of exactTools) { + if (!knownTools.has(toolName)) throw new Error(`capabilityTools names unknown tool ${toolName}`); + if (!implemented.has(toolName)) { + throw new Error(`capabilityTools advertises unimplemented tool ${toolName}`); + } + if (production && manifest.tools[toolName].protocol === 'compliance') { + throw new Error(`production runtime projections cannot select compliance tool ${toolName}`); + } + } + + const hasCapabilityScope = capabilityProtocols !== undefined || capabilityTools !== undefined; + return [...implemented] + .filter(toolName => !production || manifest.tools[toolName].protocol !== 'compliance') + .filter(toolName => ( + !hasCapabilityScope + || exactTools.has(toolName) + || protocols.has(manifest.tools[toolName].protocol) + )) + .sort(); +} + +/** Build MCP tools/list entries from already-generated per-tool bundles. */ +function buildRuntimeToolsList(projectionManifest, selectedToolNames, loadInputSchema) { + if ( + !projectionManifest + || typeof projectionManifest !== 'object' + || !projectionManifest.tools + || typeof projectionManifest.tools !== 'object' + ) { + throw new Error('projectionManifest must contain a tools object'); + } + const selected = uniqueStringSet(selectedToolNames, 'selectedToolNames', { required: true }); + if (typeof loadInputSchema !== 'function') throw new Error('loadInputSchema must be a function'); + + return [...selected].sort().map(name => { + const tool = projectionManifest.tools[name]; + if (!tool) throw new Error(`selectedToolNames names unknown projected tool ${name}`); + if (typeof tool.inputSchema !== 'string') { + throw new Error(`projected tool ${name} does not provide inputSchema`); + } + const inputSchema = loadInputSchema(tool.inputSchema, name); + if (!inputSchema || typeof inputSchema !== 'object' || Array.isArray(inputSchema)) { + throw new Error(`loadInputSchema returned an invalid schema for ${name}`); + } + return { + name, + ...(tool.summary ? { description: tool.summary } : {}), + inputSchema: clone(inputSchema), + }; + }); +} + function generateMcpSchemaProjection({ sourceDir, targetDir, @@ -680,7 +780,10 @@ function generateMcpSchemaProjection({ for (const [toolName, tool] of Object.entries(manifest.tools || {})) { if (!toolFilter(toolName, tool)) continue; - const projectedTool = { protocol: tool.protocol }; + const projectedTool = { + protocol: tool.protocol, + ...(tool.summary ? { summary: tool.summary } : {}), + }; for (const [field, relativePath] of [ ['inputSchema', tool.request_schema], ['outputSchema', tool.response_schema], @@ -739,6 +842,7 @@ module.exports = { MCP_PROTOCOL_VERSION, assertDraft07SourceSchema, assertLocalRefsResolve, + buildRuntimeToolsList, collectExternalRefs, compactDraft07Schema, enforceSchemaBounds, @@ -746,6 +850,7 @@ module.exports = { measureSchema, projectDraft07Node, projectSourceSchema, + selectRuntimeToolNames, stripModelContextAnnotations, stripPresentationAnnotations, }; diff --git a/static/schemas/source/manifest.schema.json b/static/schemas/source/manifest.schema.json index 599c7b1676..068a7c0e50 100644 --- a/static/schemas/source/manifest.schema.json +++ b/static/schemas/source/manifest.schema.json @@ -55,6 +55,12 @@ "type": "boolean", "description": "True if invoking this tool can change server-side state. New mutating tools MUST require idempotency_key (or carry an explicit naturally-idempotent exemption). A stable 3.x compatibility facade may explicitly mark the key optional while narrower replacement tools carry the required-key contract. Verb prefixes are not authoritative." }, + "summary": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "description": "Optional concise, agent-facing description suitable for live tool discovery. This is separate from the canonical request schema description and does not affect validation." + }, "operation_family": { "type": "string", "description": "Stable logical operation identity used for authorization, idempotency equivalence, task recovery, and webhook identity. Aliases in the same family share this value." diff --git a/static/schemas/source/media-buy/list-products-request.json b/static/schemas/source/media-buy/list-products-request.json index 1c4a930709..ce889e94d7 100644 --- a/static/schemas/source/media-buy/list-products-request.json +++ b/static/schemas/source/media-buy/list-products-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/list-products-request.json", "title": "List Products Request", "description": "Read seller offers with structured discovery criteria. Each invocation completes synchronously, while durable wholesale product-feed webhooks registered through sync_accounts keep buyer mirrors current without polling. list_products is the authoritative bootstrap and repair read for those subscriptions; proposal creation is handled by request_proposals.", + "x-tool-summary": "List seller products that match structured discovery criteria.", "type": "object", "x-operation-family": "list_products", "x-added-in": "3.2.0", diff --git a/static/schemas/source/protocol/get-adcp-capabilities-request.json b/static/schemas/source/protocol/get-adcp-capabilities-request.json index 9294b377ea..775bc816fa 100644 --- a/static/schemas/source/protocol/get-adcp-capabilities-request.json +++ b/static/schemas/source/protocol/get-adcp-capabilities-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/protocol/get-adcp-capabilities-request.json", "title": "Get AdCP Capabilities Request", "description": "Request payload for get_adcp_capabilities task. Protocol-level capability discovery that works across all AdCP protocols.", + "x-tool-summary": "Discover the AdCP protocols and feature sets supported by this agent.", "type": "object", "allOf": [ { diff --git a/tests/mcp-schema-projection.test.cjs b/tests/mcp-schema-projection.test.cjs index 610436f012..bffb67ce75 100644 --- a/tests/mcp-schema-projection.test.cjs +++ b/tests/mcp-schema-projection.test.cjs @@ -26,10 +26,12 @@ const { MAX_SCHEMA_OBJECTS, MCP_PROTOCOL_VERSION, assertLocalRefsResolve, + buildRuntimeToolsList, collectExternalRefs, compactDraft07Schema, measureSchema, projectDraft07Node, + selectRuntimeToolNames, stripPresentationAnnotations, stripModelContextAnnotations, } = require('../scripts/mcp-schema-projection.cjs'); @@ -804,6 +806,105 @@ test('generated production profile exposes the active 3.2 surface without compli assert.ok(profileBytes < canonicalBytes * 0.65, `${profileBytes} should be materially smaller than ${canonicalBytes}`); }); +test('runtime selection fails closed on unknown, unimplemented, and production-only capability claims', () => { + const manifest = readJson(path.join(LATEST_DIR, 'manifest.json')); + const implementedTools = ['get_adcp_capabilities', 'list_products']; + + assert.deepEqual(selectRuntimeToolNames(manifest, { implementedTools }), implementedTools); + assert.deepEqual(selectRuntimeToolNames(manifest, { + implementedTools, + capabilityProtocols: [], + capabilityTools: [], + }), []); + assert.throws( + () => selectRuntimeToolNames(manifest, { + implementedTools, + capabilityTools: ['request_proposals'], + }), + /advertises unimplemented tool request_proposals/ + ); + assert.throws( + () => selectRuntimeToolNames(manifest, { + implementedTools: [...implementedTools, 'not_an_adcp_tool'], + }), + /unknown tool not_an_adcp_tool/ + ); + assert.throws( + () => selectRuntimeToolNames(manifest, { + implementedTools: ['comply_test_controller'], + capabilityTools: ['comply_test_controller'], + }), + /cannot select compliance tool/ + ); +}); + +test('representative capability-selected media-buy runtime exposes only selected input schemas', () => { + const canonicalManifest = readJson(path.join(LATEST_DIR, 'manifest.json')); + const productionManifest = readJson(path.join(PRODUCTION_PROFILE_DIR, 'manifest.json')); + const selectedToolNames = selectRuntimeToolNames(canonicalManifest, { + implementedTools: MCP_ROLE_PROFILE_TOOLS['media-buy'], + capabilityProtocols: ['media_buy'], + capabilityTools: ['get_adcp_capabilities', 'get_task_status'], + }); + + assert.ok(selectedToolNames.includes('list_products')); + assert.ok(selectedToolNames.includes('request_proposals')); + assert.ok(selectedToolNames.includes('get_adcp_capabilities')); + assert.ok(selectedToolNames.includes('get_task_status')); + assert.ok(!selectedToolNames.includes('sync_creatives')); + assert.ok(!selectedToolNames.includes('sync_accounts')); + assert.ok( + selectedToolNames.length < Object.keys(productionManifest.tools).length / 3, + `${selectedToolNames.length} selected tools should be materially fewer than ` + + `${Object.keys(productionManifest.tools).length} production tools` + ); + + const runtimeTools = buildRuntimeToolsList( + productionManifest, + selectedToolNames, + relativePath => readJson(path.join(PRODUCTION_PROFILE_DIR, relativePath)) + ); + assert.deepEqual(runtimeTools.map(tool => tool.name), selectedToolNames); + assert.equal( + runtimeTools.find(tool => tool.name === 'get_adcp_capabilities').description, + canonicalManifest.tools.get_adcp_capabilities.summary + ); + assert.equal( + runtimeTools.find(tool => tool.name === 'list_products').description, + canonicalManifest.tools.list_products.summary + ); + for (const tool of runtimeTools) { + assert.equal(tool.outputSchema, undefined); + assert.equal(tool.inputSchema.$schema, JSON_SCHEMA_2020_12); + assert.deepEqual(collectExternalRefs(tool.inputSchema), []); + } + + const storyboardFixtures = collectStoryboardRequestFixtures(); + const draft07 = createValidator(AjvDraft07); + const draft2020 = createValidator(Ajv2020); + let parityCases = 0; + for (const toolName of selectedToolNames) { + const sourceRelativePath = canonicalManifest.tools[toolName].request_schema; + const fixtures = storyboardFixtures.get(sourceRelativePath) || []; + if (fixtures.length === 0) continue; + const sourcePath = path.join(SOURCE_DIR, sourceRelativePath); + const sourceSchema = readJson(sourcePath); + const compactSource = compactDraft07Schema(sourceSchema, sourcePath, SOURCE_DIR); + const projectedSchema = runtimeTools.find(tool => tool.name === toolName).inputSchema; + const validateSource = draft07.compile(compactSource); + const validateProjected = draft2020.compile(projectedSchema); + for (const [index, fixture] of fixtures.entries()) { + assert.equal( + validateProjected(fixture), + validateSource(fixture), + `${toolName} runtime projection changed storyboard fixture ${index}` + ); + parityCases++; + } + } + assert.ok(parityCases >= 25, `expected at least 25 selected-schema parity cases, saw ${parityCases}`); +}); + test('generated role profiles are active validation catalogs with bounded model-context views', () => { const canonicalManifest = readJson(path.join(LATEST_DIR, 'manifest.json')); From e8538f9de93a8b1abdbd3ade0dd90d43b60b0985 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 15 Aug 2026 13:02:49 +0200 Subject: [PATCH 2/2] feat(protocol): describe projected runtime tools --- ...-capability-selected-runtime-projections.md | 2 +- docs/building/by-layer/L0/schemas.mdx | 4 +++- scripts/mcp-schema-projection.cjs | 1 + .../get-account-financials-request.json | 1 + .../source/account/list-accounts-request.json | 1 + .../source/account/report-usage-request.json | 1 + .../source/account/sync-accounts-request.json | 1 + .../account/sync-governance-request.json | 1 + .../get-creative-delivery-request.json | 1 + .../get-creative-features-request.json | 1 + .../creative/list-creatives-request.json | 1 + .../creative/list-transformers-request.json | 1 + .../creative/preview-creative-request.json | 1 + .../creative/sync-creatives-request.json | 1 + .../creative/validate-input-request.json | 1 + .../media-buy/accept-proposal-request.json | 1 + .../media-buy/build-creative-request.json | 1 + .../source/media-buy/buy-products-request.json | 1 + .../media-buy/control-media-buy-request.json | 1 + .../media-buy/decline-proposals-request.json | 1 + .../get-media-buy-delivery-request.json | 1 + .../media-buy/get-media-buys-request.json | 1 + .../source/media-buy/log-event-request.json | 1 + .../provide-performance-feedback-request.json | 1 + .../media-buy/refine-proposals-request.json | 1 + .../media-buy/request-proposals-request.json | 1 + .../media-buy/sync-audiences-request.json | 1 + .../media-buy/sync-catalogs-request.json | 1 + .../media-buy/sync-event-sources-request.json | 1 + .../protocol/get-task-status-request.json | 1 + .../source/protocol/list-tasks-request.json | 1 + ...ync-agent-notification-configs-request.json | 1 + tests/mcp-schema-projection.test.cjs | 18 ++++++++++-------- 33 files changed, 44 insertions(+), 10 deletions(-) diff --git a/.changeset/add-capability-selected-runtime-projections.md b/.changeset/add-capability-selected-runtime-projections.md index 2f607c040d..838e99b49e 100644 --- a/.changeset/add-capability-selected-runtime-projections.md +++ b/.changeset/add-capability-selected-runtime-projections.md @@ -2,4 +2,4 @@ "adcontextprotocol": minor --- -Define deterministic capability-selected runtime tool projections, add optional concise manifest summaries, and keep response schemas available for lazy SDK validation outside model context. +Define deterministic capability-selected runtime tool projections, publish concise manifest summaries for every active Media Buy and Creative role tool, and keep response schemas available for lazy SDK validation outside model context. diff --git a/docs/building/by-layer/L0/schemas.mdx b/docs/building/by-layer/L0/schemas.mdx index a14a0608b6..2b3aafb616 100644 --- a/docs/building/by-layer/L0/schemas.mdx +++ b/docs/building/by-layer/L0/schemas.mdx @@ -302,7 +302,9 @@ parent response schemas and task-result resolution metadata out of band. Structural schemas deliberately omit descriptions. To support tool selection, clients combine the model-context inputs with each live MCP tool's concise `name` and `description`; the downloadable model-context manifest is not a -description catalog by itself. +description catalog by itself. Every tool in the published active Media Buy +and Creative role catalogs carries a concise manifest `summary` that a host can +use as that live description. #### Capability-selected runtime projection diff --git a/scripts/mcp-schema-projection.cjs b/scripts/mcp-schema-projection.cjs index fe7372113d..a464006140 100644 --- a/scripts/mcp-schema-projection.cjs +++ b/scripts/mcp-schema-projection.cjs @@ -18,6 +18,7 @@ const PRESENTATION_ANNOTATIONS = new Set([ 'enumDescriptions', 'examples', 'title', + 'x-tool-summary', ]); // Model prompt views communicate request shape while the parent role profile diff --git a/static/schemas/source/account/get-account-financials-request.json b/static/schemas/source/account/get-account-financials-request.json index 0fef29c4ce..cc4cce1229 100644 --- a/static/schemas/source/account/get-account-financials-request.json +++ b/static/schemas/source/account/get-account-financials-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/account/get-account-financials-request.json", "title": "Get Account Financials Request", "description": "Request financial status for an operator-billed account. Returns spend summary, credit/balance status, and invoice history. Only applicable when the seller declares account_financials capability.", + "x-tool-summary": "Retrieve spend, credit or balance status, and invoice history for an operator-billed account.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/account/list-accounts-request.json b/static/schemas/source/account/list-accounts-request.json index 0290327378..eeea84dc94 100644 --- a/static/schemas/source/account/list-accounts-request.json +++ b/static/schemas/source/account/list-accounts-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/account/list-accounts-request.json", "title": "List Accounts Request", "description": "Request parameters for listing accounts accessible to the authenticated agent. For upstream-managed namespaces, this discovers seller/storefront account_id values. For buyer-declared accounts, responses return the natural-key fields needed to reconstruct an AccountRef after a cold start.", + "x-tool-summary": "List accounts accessible to the caller and recover their canonical account references.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/account/report-usage-request.json b/static/schemas/source/account/report-usage-request.json index ac3c1ecef4..ed9d101923 100644 --- a/static/schemas/source/account/report-usage-request.json +++ b/static/schemas/source/account/report-usage-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/account/report-usage-request.json", "title": "Report Usage Request", "description": "Reports how a vendor's service was consumed after campaign delivery. Used by orchestrators (DSPs, storefronts) to inform vendor agents (signals, governance, creative, or — when the receiving agent is the seller of the media buy itself — a sales agent for buyer-attested or vendor-attested billing reconciliation) what was used so the receiver can track earned revenue and verify billing. Records can span multiple accounts and campaigns in a single request. When the buy's `measurement_terms.billing_measurement.vendor` names a party other than the seller's own ad server, that party (or the buyer on its behalf) pushes final measurements via this task; the seller invoices against those final records. See [Billing authority](/docs/media-buy/advanced-topics/billing-authority) for the end-to-end flow.", + "x-tool-summary": "Report vendor-service consumption and final measurements for revenue tracking and billing reconciliation.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/account/sync-accounts-request.json b/static/schemas/source/account/sync-accounts-request.json index 21da0f0d0a..9841ffbf30 100644 --- a/static/schemas/source/account/sync-accounts-request.json +++ b/static/schemas/source/account/sync-accounts-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/account/sync-accounts-request.json", "title": "Sync Accounts Request", "description": "Sync advertiser account state with a seller. Two modes, distinguished by the key on each per-account entry:\n\n- **Provisioning mode** (`brand` + `operator` + `billing` at the entry root): the agent declares the advertiser identity, operator, optional operator-owned buying unit, optional fixed account currency, sandbox disposition, and billing model. The seller provisions or links the corresponding advertiser object via upsert. `brand.countries`, `operator_unit.id`, `currency`, and `sandbox` participate in the buyer-declared natural key when present; `operator_unit.name` is display metadata only. The seller MAY echo a seller-assigned account_id but MUST continue accepting the complete natural-key AccountRef.\n\n- **Settings-update mode** (`account` field carrying an [`AccountRef`](/schemas/core/account-ref.json)): targets an existing account by seller/storefront `account_id` or buyer-declared natural key. The seller updates settable state without provisioning side effects.\n\nExactly one key shape is allowed per entry. Sellers that do not implement one mode return `UNSUPPORTED_PROVISIONING` for that mode.", + "x-tool-summary": "Provision advertiser accounts or update settings for existing accounts through declarative synchronization.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/account/sync-governance-request.json b/static/schemas/source/account/sync-governance-request.json index cffac71309..bb5f769220 100644 --- a/static/schemas/source/account/sync-governance-request.json +++ b/static/schemas/source/account/sync-governance-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/account/sync-governance-request.json", "title": "Sync Governance Request", "description": "Sync the governance agent endpoint against specific accounts. The service persists the governance agent and calls it for approval during governed lifecycle events via check_governance. Uses replace semantics: each call replaces any previously synced agent on the specified accounts. The service MUST verify that the authenticated agent has authority over each referenced account before persisting the governance agent.\n\nThe binding is **account-scoped, not plan-scoped**. Each account binds to exactly one governance agent, and that agent owns the lifecycle for every plan on the account. Initial buyer-side checks address a plan by plan_id; downstream services pass only the opaque governance_context, from which the issuing agent recovers the plan. Governance registration does not vary per plan.\n\nA plan is unitary — budget authority, delivery monitoring, and regulatory compliance are phases of the same evaluation (`purchase` / `modification` / `delivery` on check_governance), not specialisms held by different agents — so a single agent owns the full lifecycle. Buyers that need internal specialist review compose that inside the governance agent, not at the registration layer. `governance_agents` is an array (not a scalar) because that is the shape 3.0 shipped with and existing senders MUST continue to work; the `maxItems: 1` constraint is load-bearing. The single-agent rule is also baked into the singular governance_context carried across tasks.", + "x-tool-summary": "Bind or replace the governance agent used for approval across specific advertiser accounts.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/creative/get-creative-delivery-request.json b/static/schemas/source/creative/get-creative-delivery-request.json index 02e27d3607..af8840f104 100644 --- a/static/schemas/source/creative/get-creative-delivery-request.json +++ b/static/schemas/source/creative/get-creative-delivery-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/get-creative-delivery-request.json", "title": "Get Creative Delivery Request", "description": "Request parameters for retrieving creative delivery data including variant-level metrics from a creative agent. At least one scoping filter (media_buy_ids or creative_ids) is required.", + "x-tool-summary": "Retrieve creative and variant-level delivery metrics scoped to media buys or creative IDs.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/creative/get-creative-features-request.json b/static/schemas/source/creative/get-creative-features-request.json index 715315dd65..314ce2663c 100644 --- a/static/schemas/source/creative/get-creative-features-request.json +++ b/static/schemas/source/creative/get-creative-features-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/get-creative-features-request.json", "title": "Get Creative Features Request", "description": "Request payload for the get_creative_features task. Submits a creative manifest for evaluation by a governance agent, which analyzes the creative and returns scored feature values (brand safety, content categorization, quality metrics, etc.).", + "x-tool-summary": "Evaluate a creative manifest and return scored safety, content, quality, and governance features.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/creative/list-creatives-request.json b/static/schemas/source/creative/list-creatives-request.json index a862a1d25f..484becf9c1 100644 --- a/static/schemas/source/creative/list-creatives-request.json +++ b/static/schemas/source/creative/list-creatives-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/list-creatives-request.json", "title": "List Creatives Request", "description": "Request parameters for querying creative assets from a creative library with filtering, sorting, and pagination. Implemented by any agent that hosts a creative library — creative agents (ad servers, creative platforms) and sales agents that manage creatives.", + "x-tool-summary": "List creative-library assets with filtering, sorting, and pagination.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/creative/list-transformers-request.json b/static/schemas/source/creative/list-transformers-request.json index 6ecc66fc31..eb8f8906db 100644 --- a/static/schemas/source/creative/list-transformers-request.json +++ b/static/schemas/source/creative/list-transformers-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/list-transformers-request.json", "title": "List Transformers Request (Creative Agent)", "description": "Request parameters for discovering account-scoped creative transformers offered by this creative agent. Brief-filterable and paginated. Transformers are the creative analog of media-buy products: agent-offered, account-scoped, selectable units of build capability. Use `expand_params` to additionally return account-scoped option VALUES (e.g. the buyer's configured voices) for named enumerable params.", + "x-tool-summary": "Discover account-scoped creative transformers and their selectable configuration options.", "type": "object", "allOf": [ { "$ref": "/schemas/core/version-envelope.json" }, diff --git a/static/schemas/source/creative/preview-creative-request.json b/static/schemas/source/creative/preview-creative-request.json index 951c9cf74a..fa9d6dc888 100644 --- a/static/schemas/source/creative/preview-creative-request.json +++ b/static/schemas/source/creative/preview-creative-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/preview-creative-request.json", "title": "Preview Creative Request", "description": "Request to generate previews of creative manifests. Uses request_type to select single, batch, or variant mode.", + "x-tool-summary": "Generate single, batch, or variant previews of creative manifests.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/creative/sync-creatives-request.json b/static/schemas/source/creative/sync-creatives-request.json index 586a8e9767..367d2e9480 100644 --- a/static/schemas/source/creative/sync-creatives-request.json +++ b/static/schemas/source/creative/sync-creatives-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/sync-creatives-request.json", "title": "Sync Creatives Request", "description": "Request parameters for syncing creative assets with upsert semantics - supports bulk operations, scoped updates, and assignment management", + "x-tool-summary": "List or synchronize creative assets, scoped updates, and media-buy assignments.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/creative/validate-input-request.json b/static/schemas/source/creative/validate-input-request.json index 5391517669..603f6b1376 100644 --- a/static/schemas/source/creative/validate-input-request.json +++ b/static/schemas/source/creative/validate-input-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/creative/validate-input-request.json", "title": "Validate Input Request", "description": "Request payload for the validate_input task. Lets buyers preflight a creative manifest against canonical formats and/or specific products before committing to a render or other expensive creative-production step. It validates manifest structure and target format constraints only: slot counts, asset types, parameter ranges, and format-shape constraints. It does not rehearse a seller's library mutation, creative_id upsert semantics, package assignments, account authorization, active-delivery protections, or other trafficking-time gates. When the question is final seller acceptance for a creative that is ready to traffic, call sync_creatives directly; use dry_run: true for a non-mutating rehearsal of that operation. Used by build_creative internally to validate inputs before producing output. For genuinely nondeterministic generative systems where predictive validation is impossible, the platform's own post-synthesis QA loop applies — validate_input is the predictable-case primitive.\n\nThe `targets[]` array is a discriminated list of validation targets, mirroring the response shape on `validate-input-result.json#target`. Each entry has a `kind` (canonical | product | third_party_format | capability) plus a kind-specific identifier. The capability kind is the canonical agent-local route for an advertised supported_formats entry whose operations contains validate; the other target kinds remain portable contract identifiers. Discriminated-by-kind on both sides eliminates a wire-shape mismatch: a previous draft used `format_ids: string[]` for canonical names alongside `product_ids: string[]`, which collided with `Product.format_ids: FormatId[]` ({agent_url, id}) — codegen would emit the same field name with two different types. Discriminated `targets[]` makes the intent explicit and codegen-clean.", + "x-tool-summary": "Preflight a creative manifest against canonical formats, products, third-party formats, or agent capabilities.", "type": "object", "required": ["manifest"], "properties": { diff --git a/static/schemas/source/media-buy/accept-proposal-request.json b/static/schemas/source/media-buy/accept-proposal-request.json index ccbf7d298b..cb6d114673 100644 --- a/static/schemas/source/media-buy/accept-proposal-request.json +++ b/static/schemas/source/media-buy/accept-proposal-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/accept-proposal-request.json", "title": "Accept Proposal Request", "description": "Accept one committed immutable proposal. Depending on proposal_kind, acceptance creates a MediaBuy, applies an amendment, or applies a negotiated cancellation. The proposal already contains the commercial terms, so callers do not repeat packages, dates, targeting, or creatives.", + "x-tool-summary": "Accept a committed proposal to create or amend a media buy, or apply a negotiated cancellation.", "type": "object", "x-mutates-state": true, "x-governed-commitment": { diff --git a/static/schemas/source/media-buy/build-creative-request.json b/static/schemas/source/media-buy/build-creative-request.json index 3cf4687e18..41eaa593bc 100644 --- a/static/schemas/source/media-buy/build-creative-request.json +++ b/static/schemas/source/media-buy/build-creative-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/build-creative-request.json", "title": "Build Creative Request", "description": "Request to transform, generate, refine, or retrieve a creative manifest. Supports four modes: (1) generation from a brief or seed assets, (2) transformation of an existing manifest, (3) refinement of a prior produced variant via refine_from_build_variant_id (re-build with a natural-language message + config delta), (4) retrieval from a creative library by creative_id. Produces target manifest(s) through canonical capabilities advertised in `get_adcp_capabilities.creative.supported_formats[]`. In 3.2, provide `target_capability_id` for one output or `target_capability_ids` for multiple outputs. Legacy `target_format_id` / `target_format_ids` remain accepted during the 3.x compatibility window but are deprecated.", + "x-tool-summary": "Generate, transform, refine, retrieve, or adapt creative manifests for advertised creative capabilities.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/buy-products-request.json b/static/schemas/source/media-buy/buy-products-request.json index 8bb2276b72..96ddeeed00 100644 --- a/static/schemas/source/media-buy/buy-products-request.json +++ b/static/schemas/source/media-buy/buy-products-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/buy-products-request.json", "title": "Buy Products Request", "description": "Create a MediaBuy directly from published product offers. This buyer-composed path accepts published commercial terms, supports targeting and delivery controls, and never accepts inline creatives or creative assignments. The seller records an immutable accepted proposal snapshot so later commercial amendments can use refine_proposals.", + "x-tool-summary": "Create a media buy directly from published seller products and record its accepted commercial snapshot.", "type": "object", "x-mutates-state": true, "x-governed-commitment": { "scope": "always" }, diff --git a/static/schemas/source/media-buy/control-media-buy-request.json b/static/schemas/source/media-buy/control-media-buy-request.json index 5430711c09..86ac07f770 100644 --- a/static/schemas/source/media-buy/control-media-buy-request.json +++ b/static/schemas/source/media-buy/control-media-buy-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/control-media-buy-request.json", "title": "Control Media Buy Request", "description": "Apply operational delivery controls inside the MediaBuy's accepted proposal envelope. Sellers return REQUOTE_REQUIRED when budget, targeting, or another control would change the commercial envelope; the buyer then forks the accepted proposal through refine_proposals. Creative mutation, new products/packages, flight changes, pricing changes, and billing-term changes are not accepted here.", + "x-tool-summary": "Pause, resume, cancel, or adjust delivery controls without changing accepted commercial terms.", "type": "object", "x-mutates-state": true, "x-governed-commitment": { diff --git a/static/schemas/source/media-buy/decline-proposals-request.json b/static/schemas/source/media-buy/decline-proposals-request.json index adb0910715..793ceb7c25 100644 --- a/static/schemas/source/media-buy/decline-proposals-request.json +++ b/static/schemas/source/media-buy/decline-proposals-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/decline-proposals-request.json", "title": "Decline Proposals Request", "description": "Terminally decline one or more immutable proposals. Repeating an already-applied decline with a new idempotency key succeeds with outcome declined and preserves the first recorded reason and detail; the proposal cannot later be executed.", + "x-tool-summary": "Terminally decline one or more proposals and record the buyer's reason.", "type": "object", "x-mutates-state": true, "x-operation-family": "decline_proposals", diff --git a/static/schemas/source/media-buy/get-media-buy-delivery-request.json b/static/schemas/source/media-buy/get-media-buy-delivery-request.json index 185a565edf..8f65ebf994 100644 --- a/static/schemas/source/media-buy/get-media-buy-delivery-request.json +++ b/static/schemas/source/media-buy/get-media-buy-delivery-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/get-media-buy-delivery-request.json", "title": "Get Media Buy Delivery Request", "description": "Request parameters for retrieving comprehensive delivery metrics", + "x-tool-summary": "Retrieve media-buy delivery metrics with optional date, package, window, attribution, and dimension breakdowns.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/get-media-buys-request.json b/static/schemas/source/media-buy/get-media-buys-request.json index fd8dfb5eab..9ba3ed8522 100644 --- a/static/schemas/source/media-buy/get-media-buys-request.json +++ b/static/schemas/source/media-buy/get-media-buys-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/get-media-buys-request.json", "title": "Get Media Buys Request", "description": "Request parameters for retrieving media buy status, creative approval state, and optional delivery snapshots", + "x-tool-summary": "List media buys with current status, creative approval state, and optional delivery snapshots.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/log-event-request.json b/static/schemas/source/media-buy/log-event-request.json index 816243235f..0e961cb029 100644 --- a/static/schemas/source/media-buy/log-event-request.json +++ b/static/schemas/source/media-buy/log-event-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/log-event-request.json", "title": "Log Event Request", "description": "Request parameters for logging marketing events", + "x-tool-summary": "Log batches of marketing events against a configured event source for measurement and attribution.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/provide-performance-feedback-request.json b/static/schemas/source/media-buy/provide-performance-feedback-request.json index f8cb4f6b57..1231ad8da4 100644 --- a/static/schemas/source/media-buy/provide-performance-feedback-request.json +++ b/static/schemas/source/media-buy/provide-performance-feedback-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/provide-performance-feedback-request.json", "title": "Provide Performance Feedback Request", "description": "Submits one compact optimizer-ready assertion. Measurement agents submit to a buyer-controlled orchestrator gateway through this task; the orchestrator validates, normalizes, and forwards feedback to sellers under the buyer's identity.", + "x-tool-summary": "Submit an optimizer-ready performance assertion for validation and forwarding to a seller.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/refine-proposals-request.json b/static/schemas/source/media-buy/refine-proposals-request.json index f738e98f01..e606b6a128 100644 --- a/static/schemas/source/media-buy/refine-proposals-request.json +++ b/static/schemas/source/media-buy/refine-proposals-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/refine-proposals-request.json", "title": "Refine Proposals Request", "description": "Create draft revisions or finalize draft proposals into held committed snapshots. Each result receives a new proposal_id and every source remains unchanged.", + "x-tool-summary": "Revise draft proposals or finalize them into committed, inventory-held snapshots.", "type": "object", "x-mutates-state": true, "x-operation-family": "refine_proposals", diff --git a/static/schemas/source/media-buy/request-proposals-request.json b/static/schemas/source/media-buy/request-proposals-request.json index eb0d98ed93..0a90083897 100644 --- a/static/schemas/source/media-buy/request-proposals-request.json +++ b/static/schemas/source/media-buy/request-proposals-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/request-proposals-request.json", "title": "Request Proposals Request", "description": "Ask a seller to create one or more indicative media-plan proposals from a brief. Each returned proposal_id identifies one immutable draft snapshot; revise or finalize it through refine_proposals, or decline it through decline_proposals.", + "x-tool-summary": "Ask a seller to create one or more draft media-plan proposals from a campaign brief.", "type": "object", "x-mutates-state": true, "x-operation-family": "request_proposals", diff --git a/static/schemas/source/media-buy/sync-audiences-request.json b/static/schemas/source/media-buy/sync-audiences-request.json index 4a9198f40e..38b549f636 100644 --- a/static/schemas/source/media-buy/sync-audiences-request.json +++ b/static/schemas/source/media-buy/sync-audiences-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/sync-audiences-request.json", "title": "Sync Audiences Request", "description": "Request parameters for managing CRM-based audiences on an account with upsert semantics. Existing audiences matched by audience_id are updated, new ones are created. Members are specified as delta operations: add appends new members, remove drops existing ones. Recommend no more than 100,000 members per call; for larger lists, chunk and call incrementally using add/remove deltas. When delete_missing is true, buyer-managed audiences on the account not in this request are removed — do not combine with omitted audiences or all buyer-managed audiences will be deleted. When audiences is omitted, the call is discovery-only: it returns all audiences on the account without modification.", + "x-tool-summary": "List or synchronize account audiences with upsert, member-delta, and optional replacement semantics.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/sync-catalogs-request.json b/static/schemas/source/media-buy/sync-catalogs-request.json index caf9406291..19a3e51311 100644 --- a/static/schemas/source/media-buy/sync-catalogs-request.json +++ b/static/schemas/source/media-buy/sync-catalogs-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/sync-catalogs-request.json", "title": "Sync Catalogs Request", "description": "Request parameters for syncing catalog feeds with upsert semantics. Supports bulk operations across multiple catalog types (products, inventory, stores, promotions, offerings). Existing catalogs matched by catalog_id are updated, new ones are created. When catalogs is omitted, the call is discovery-only: returns all catalogs on the account without modification.", + "x-tool-summary": "List or synchronize account catalog feeds across products, inventory, stores, promotions, and offerings.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/media-buy/sync-event-sources-request.json b/static/schemas/source/media-buy/sync-event-sources-request.json index 54fbe63b0e..dbf73046c0 100644 --- a/static/schemas/source/media-buy/sync-event-sources-request.json +++ b/static/schemas/source/media-buy/sync-event-sources-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/media-buy/sync-event-sources-request.json", "title": "Sync Event Sources Request", "description": "Request parameters for configuring event sources on an account with upsert semantics. Existing event sources matched by event_source_id are updated, new ones are created. When delete_missing is true, buyer-managed event sources on the account not in this request are removed. When event_sources is omitted, the call is discovery-only: it returns all event sources on the account without modification. The response always includes both synced and seller-managed event sources for full visibility.", + "x-tool-summary": "List or synchronize account event sources with upsert and optional replacement semantics.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/protocol/get-task-status-request.json b/static/schemas/source/protocol/get-task-status-request.json index 24dcb68080..d46735b1fd 100644 --- a/static/schemas/source/protocol/get-task-status-request.json +++ b/static/schemas/source/protocol/get-task-status-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/protocol/get-task-status-request.json", "title": "Get Task Status Request", "description": "Request parameters for the get_task_status AdCP tool. This is a 3.x protocol-namespace alias for the legacy AdCP tasks/get polling surface, not a transport-native MCP/A2A tasks/* method. It retrieves a specific AdCP application-layer async task by ID with optional conversation history and terminal result payload.", + "x-tool-summary": "Retrieve an AdCP asynchronous task, its conversation history, and terminal result by task ID.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/protocol/list-tasks-request.json b/static/schemas/source/protocol/list-tasks-request.json index 91b7e39094..225626ea92 100644 --- a/static/schemas/source/protocol/list-tasks-request.json +++ b/static/schemas/source/protocol/list-tasks-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/protocol/list-tasks-request.json", "title": "List Tasks Request", "description": "Request parameters for the list_tasks AdCP tool. This is a 3.x protocol-namespace alias for the legacy AdCP tasks/list reconciliation surface, not a transport-native MCP/A2A tasks/* method. It lists and filters AdCP application-layer async tasks for state reconciliation while preserving the legacy 3.x payload shape.", + "x-tool-summary": "List and filter AdCP asynchronous tasks for state reconciliation.", "type": "object", "allOf": [ { diff --git a/static/schemas/source/protocol/sync-agent-notification-configs-request.json b/static/schemas/source/protocol/sync-agent-notification-configs-request.json index 21a5b04d23..f751e2faeb 100644 --- a/static/schemas/source/protocol/sync-agent-notification-configs-request.json +++ b/static/schemas/source/protocol/sync-agent-notification-configs-request.json @@ -3,6 +3,7 @@ "$id": "/schemas/protocol/sync-agent-notification-configs-request.json", "title": "Sync Agent Notification Configs Request", "description": "Register, replace, pause, or clear caller-scoped agent-level webhook subscribers. This task manages notifications whose lifecycle belongs to the seller agent itself rather than to an account or media buy. The initial use case is `capabilities.changed`, which invalidates cached `get_adcp_capabilities` responses. Declarative semantics match account-level `notification_configs[]`, but the replacement is scoped to the authenticated caller or registry identity: the submitted array is that caller's complete desired set, keyed by `subscriber_id`; persisted subscribers for that caller whose `subscriber_id` does not appear in the submitted array are removed; sending `[]` clears only that caller's set. Sellers MUST reject this task unless `get_adcp_capabilities.adcp.capability_changes.notifications.supported` is true.", + "x-tool-summary": "Register, replace, pause, or clear caller-scoped webhooks for agent-level notifications.", "type": "object", "allOf": [ { diff --git a/tests/mcp-schema-projection.test.cjs b/tests/mcp-schema-projection.test.cjs index bffb67ce75..1c2095e231 100644 --- a/tests/mcp-schema-projection.test.cjs +++ b/tests/mcp-schema-projection.test.cjs @@ -865,15 +865,10 @@ test('representative capability-selected media-buy runtime exposes only selected relativePath => readJson(path.join(PRODUCTION_PROFILE_DIR, relativePath)) ); assert.deepEqual(runtimeTools.map(tool => tool.name), selectedToolNames); - assert.equal( - runtimeTools.find(tool => tool.name === 'get_adcp_capabilities').description, - canonicalManifest.tools.get_adcp_capabilities.summary - ); - assert.equal( - runtimeTools.find(tool => tool.name === 'list_products').description, - canonicalManifest.tools.list_products.summary - ); for (const tool of runtimeTools) { + assert.equal(tool.description, canonicalManifest.tools[tool.name].summary); + assert.ok(tool.description.length > 0); + assert.equal(tool.inputSchema['x-tool-summary'], undefined); assert.equal(tool.outputSchema, undefined); assert.equal(tool.inputSchema.$schema, JSON_SCHEMA_2020_12); assert.deepEqual(collectExternalRefs(tool.inputSchema), []); @@ -957,6 +952,13 @@ test('generated role profiles are active validation catalogs with bounded model- for (const toolName of expectedTools) { const fullTool = profile.tools[toolName]; const modelTool = modelContext.tools[toolName]; + assert.ok(canonicalManifest.tools[toolName].summary, `${toolName} must provide a runtime summary`); + assert.ok( + canonicalManifest.tools[toolName].summary.length <= 160, + `${toolName} runtime summary must stay concise` + ); + assert.equal(fullTool.summary, canonicalManifest.tools[toolName].summary); + assert.equal(modelTool.summary, fullTool.summary); assert.equal(fullTool.protocol, canonicalManifest.tools[toolName].protocol); assert.equal(modelTool.protocol, fullTool.protocol); assert.equal(modelTool.inputSchema, fullTool.inputSchema);