> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing Response Size

> How to keep AdCP responses lean with proposal planning, product field selection, pagination, feed versioning, and client-side projection.

When a buyer agent calls an AdCP seller, the response can range from a few
seller-planned proposals to a wholesale mirror containing detailed product,
signal, and placement metadata. This page covers the controls that keep those
responses right-sized—and the client-side projection that matters most.

## Interoperability size target

MCP hosts and gateways impose different undocumented response ceilings. For pageable reads and other read-only tasks that offer field projection, AdCP producers SHOULD keep each serialized MCP response at or below **128 KiB (131,072 bytes)**. This is an interoperability target, not permission for a client to allocate without bounds: clients SHOULD still enforce the streamed whole-message cap described in [MCP response extraction](/dist/docs/3.3.0-beta.0/building/by-layer/L0/mcp-response-extraction#size-limits), which is itself one instance of the general client obligation to [bound every agent response](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#response-size-bounds). That cap defaults to 4 MiB, which is also the ceiling on what any client default may be. The 32x gap between the two numbers is not slack a producer may spend: 128 KiB is what a cooperating producer aims at, and 4 MiB is what a client tolerates from one that is hostile or broken.

For those read paths, use the task's cursor, reduce the requested fields or breakdowns, or return a smaller schema-valid page with continuation metadata before the response exceeds 128 KiB. Do not invent a partial response for a task whose schema does not define one.

Do not apply the 128 KiB target by turning a completed mutation into an error. If a producer can determine before execution that it cannot return the mutation's required result within a host limit, it MUST reject the request before side effects. Once side effects occur, it MUST return the schema-valid committed result or a task-defined asynchronous handoff; it must not report failure merely to satisfy the size target.

`tools/list` is cursor-pageable at the MCP layer even though it is not an AdCP task. Servers SHOULD paginate the live tool catalog so each page remains under the same 128 KiB target. Clients that need the complete catalog MUST follow `nextCursor` until it is absent. Capability-selected tool registration and concise `x-tool-summary` descriptions reduce each page further; do not publish the full AdCP catalog, response schemas, or long reference prose in a session's `tools/list` result. An unpaginated catalog that embeds projected `inputSchema` documents grows fast: AdCP's own reference controller projects to 1,220,495 bytes, nearly ten times the 128 KiB target, and a reference seller implementation has served a 23 MB `tools/list` — past any client cap, which made that agent unreachable rather than merely slow. Pagination is what keeps `tools/list` inside the target; staying inside the client cap as well is the floor, not the goal.

## Discovery responses stay small

Discovery is where an oversized response stops being a degraded experience and becomes a dead end. `tools/list`, `get_adcp_capabilities`, and the A2A agent card are the first bytes any counterparty reads from you, and the read aborts mid-body with no `adcp_error` to branch on. Every later size control on this page assumes discovery already succeeded.

Agents SHOULD keep all three surfaces well under the default client cap, and SHOULD paginate a large catalog rather than serve it whole. Only one of these targets is a normative constant; the other two are editorial:

| Surface | Target | Basis |
| - | - | - |
| `tools/list` (per page) | 128 KiB | The [interoperability target](#interoperability-size-target) above. Page size is the *server's* choice — MCP's `cursor` has no page-size parameter — so a buyer cannot ask for less |
| `get_adcp_capabilities` | 64 KiB | `MAX_CAPABILITIES_BYTES`, a normative fetch budget on the [signing-key discovery path](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#discovering-an-agents-signing-keys-via-brand_json_url). Binding on any agent that signs |
| A2A agent card | 64 KiB | No normative cap exists; the general client bound applies. AdCP editorial figure, by analogy with the other unauthenticated well-known JSON documents a counterparty fetches (`MAX_JWKS_BYTES`, `MAX_CAPABILITIES_BYTES`) |

**Read the agent-card row as a target, not a threshold.** A client MUST NOT reject an agent card at 64 KiB — the applicable bound is the 4 MiB client default, and an agent serving a larger card is conformant. The target exists because the card is fetched pre-authentication by every counterparty and every crawler, and because it has no narrowing lever at all: a plain `GET`, with the authenticated extended card larger rather than smaller.

**`get_adcp_capabilities` is the sharpest of the three**, because 64 KiB is not merely a client default there. The signing-key discovery path fetches capabilities under `MAX_CAPABILITIES_BYTES` without narrowing, so a document above that budget fails with `request_signature_capabilities_unreachable` and the agent's signing keys stop resolving — immediately for any verifier without a live cached mapping, and within one cache cycle for the rest. That binds any agent that signs requests or webhooks, or that publishes `identity.key_origins`. A purely non-signing agent is bounded only by the client default, but holding 64 KiB is what lets it start signing later without a discovery break. A buyer *can* narrow an ordinary capabilities call with `protocols[]`; the signing-key path cannot.

Practical levers, in the order worth reaching for:

1. **Register tools by capability.** Publish the tools this session can actually call, not the full AdCP catalog. This is usually the single largest reduction.
2. **Keep tool descriptions terse.** Use `x-tool-summary`; reference documentation by link rather than inlining it. Long reference prose in a tool description is paid for on every session.
3. **Do not inline response schemas.** Projected `inputSchema` documents are the usual reason a catalog clears a megabyte.
4. **Paginate what remains.** `tools/list` is cursor-pageable at the MCP layer; clients that need the whole catalog follow `nextCursor` until it is absent.
5. **Trim capability declarations to what you support.** A capabilities document is a declaration, not a product catalog — move anything enumerable behind a task that can paginate.

**Not every byte is self-declared.** Two fields enumerate relationships a *counterparty* asserts: `media_buy.portfolio.publisher_domains` and `signals.data_provider_domains` are populated from those counterparties' own `adagents.json`, which any domain owner writes unilaterally and without your consent. Mirror either one wholesale and a stranger can inflate your capabilities document past 64 KiB — and since the signing-key path does not narrow, what breaks is your signature verification, a cache cycle after the fact. From 3.3 a normative byte budget caps any single counterparty-asserted field at 8,192 bytes and both together at 16,384, measured as uncompressed serialized JSON; the structural bounds (100 unique entries, 253 chars each) are graded by the conformance storyboard and become schema constraints in 4.0. Select what you actually represent rather than reflecting whatever asserts you; where the real set does not fit, truncate and set `publisher_domains_complete: false` (or `data_provider_domains_complete: false`) so buyers resolve authorization per-publisher instead of reading your sample as an allowlist. See [Response size bounds](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#response-size-bounds).

**One known tension.** `creative.supported_formats` is the 3.2 source of truth for format discovery — it replaced the pageable `list_creative_formats` task — so a creative agent with a real format catalog is pushed toward a large capabilities document by the same spec that asks it to stay under 64 KiB. Declare the format contracts you actually build, and track [#8198](https://github.com/adcontextprotocol/adcp/issues/8198). Note that the budgets above are sub-caps, not an allowance: bounding the counterparty-asserted fields does not buy headroom for a catalog, and a document that clears 64 KiB on self-declared content alone fails the same way.

A seller whose discovery response only succeeds against a buyer who raised its cap has not shipped a working integration. It works against that one buyer, against the defaults of none.

## Wire response ≠ model context

The single most important point: **the bytes on the wire are not what your model has to consume.**

AdCP responses are structured data. An MCP response arrives as `structuredContent` — typed JSON that the client parses before any model sees it. An A2A response pairs a human-readable `TextPart` with an authoritative `DataPart`. In both cases, a well-built client stores the full response and projects or summarizes it before prompting the next model turn.

```
Seller agent → structured response (full data)
                    ↓
Client stores raw response
                    ↓
Client projects / summarizes → model context (lean)
```

If your agent is forwarding raw tool results into the model context unchanged, the fix is in the client, not the protocol. Store the response, extract what the model needs for its next decision, and reference the stored data by ID when details are needed later.

> [!WARNING]
>
> **Naive-host caveat.** Some MCP hosts pass the entire `structuredContent` blob into the model context as-is. If you control the host, project before prompting. If you don't, the controls below become your primary defense against oversized context.

## Ask for a plan when you want curation

Use `request_proposals` when the seller should turn a campaign brief into a
small set of actionable plans:

```json theme={null}
{
  "adcp_version": "3.2",
  "idempotency_key": "550e8400-e29b-41d4-a716-446655442001",
  "account": { "account_id": "account_123" },
  "brand": { "domain": "acme-outdoor.example" },
  "brief": "Build a concise premium-video plan for Acme Outdoor's Q4 launch. Prioritize completed views.",
  "criteria": {
    "offer_filters": { "channels": ["olv"] },
    "targeting_overlay": { "geo_countries": ["US"] }
  }
}
```

The seller returns immutable draft proposals rather than a catalog page. Use
typed `refine_proposals` requests to narrow the plan without retransmitting the
full source response.

Use `list_products` instead when the buyer wants published offers or maintains
a wholesale mirror. A mirror is a feed-synchronization topology, not a cheaper
proposal mode.

## `fields` selector for lightweight discovery

When you only need a subset of published product data, pass `fields` to
`list_products`:

```json theme={null}
{
  "adcp_version": "3.2",
  "account": { "account_id": "account_123" },
  "criteria": {
    "offer_filters": { "channels": ["olv"] }
  },
  "fields": ["product_id", "name", "pricing_options"],
  "max_results": 10
}
```

This is useful when:

* Your agent is scanning multiple sellers and only needs IDs and prices for initial comparison
* You want to skip heavy fields like `product_card`, `product_card_detailed`, `placements`, or signal metadata
* You're building a summary view before drilling into specific products

Without `fields`, the seller returns the full product object — including visual card definitions, placement specs, and any bundled signal metadata. For a first-pass discovery across multiple sellers, that's more data than you need.

## Pagination for cardinality control

`list_products` uses cursor-based pagination:

| Parameter | Description |
| - | - |
| `max_results` | Maximum products per page |
| `cursor` | Opaque cursor from the previous response |

Response:

| Field | Description |
| - | - |
| `next_cursor` | Cursor for the next page; omitted on the terminal page |
| `feed_version` | Opaque version of the selected offer feed |
| `pricing_version` | Optional separately versioned pricing layer |
| `cache_scope` | Whether versions describe the public or account layer |

Pass `next_cursor` into the next `list_products` request. A mirror stores
`feed_version` with its `cache_scope` and uses account-level product webhooks
for routine changes. Conditional reads with `if_feed_version` repair gaps or
confirm that a selected feed is unchanged; they do not replace a healthy
webhook stream.

**Paginate; do not ask buyers to raise their limits.** A seller whose catalog
does not fit inside one response SHOULD return a smaller page with
`next_cursor` rather than emit a single large one. Buyers
[bound the size of every agent response they read](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#response-size-bounds)
and surface an over-limit response as an error, not as a truncated page, so an
oversized reply is not a slow success — it is a failure, and it arrives as a
mid-body connection abort rather than as a protocol error the buyer can
interpret. A page size that only works against a buyer who raised its limit for
you is not interoperable. The same holds for the cursor-pageable `tools/list`
catalog and for every other pageable read in this specification.

## Truncation flags on delivery

[`get_media_buy_delivery`](/dist/docs/3.3.0-beta.0/media-buy/task-reference/get_media_buy_delivery) returns breakdown arrays (by geo, by creative, by day, etc.) that can grow large. Each breakdown array has a sibling boolean flag — `by_geo_truncated`, `by_creative_truncated`, etc. — that tells you whether the returned rows are the complete set or just the top-N:

| Flag value | Meaning |
| - | - |
| `false` | All rows are present |
| `true` | Additional rows exist beyond what was returned |

When a flag is `true`, the returned rows are ordered by the applied sort metric in the applied direction (descending by default — the most significant breakdowns; `sort_direction: "asc"` deliberately returns the least significant, e.g. worst performers) and the tail is omitted. The response echoes the applied sort per breakdown (`by_X_sorted_by` / `by_X_sort_direction`), so a fallback to spend is visible. This is by design: delivery breakdowns are for optimization decisions, not archival reporting. If you need the full dataset, use the seller's native reporting API.

## Putting it together

A token-efficient AdCP integration follows this pattern:

1. **Use `request_proposals` for seller curation.** Ask for plans instead of downloading a feed and prompting a model to rediscover seller expertise.
2. **Use typed `refine_proposals`.** Carry proposal IDs and changes, not the full prior response, into the next turn.
3. **Store raw responses client-side.** Don't feed full product objects into the model context. Extract what matters, summarize the rest.
4. **Read truncation flags before paginating delivery.** If `by_geo_truncated: false`, you already have everything — no need for follow-up calls.
5. **Treat wholesale as synchronization.** Bootstrap with `list_products`, apply product webhooks, and use versioned reads for repair.

> **[Discovery and planning](/dist/docs/3.3.0-beta.0/media-buy/product-discovery/)**
>
> Choose published offers, wholesale mirroring, or seller-planned proposals.

> **[list\_products reference](/dist/docs/3.3.0-beta.0/media-buy/task-reference/list_products)**
>
> Field selection, cursor pagination, feed versions, and mirror repair.

> **[Delivery reporting](/dist/docs/3.3.0-beta.0/media-buy/task-reference/get_media_buy_delivery)**
>
> Breakdown arrays, truncation flags, and sort semantics.

> **[How agents communicate](/dist/docs/3.3.0-beta.0/building/concepts/how-agents-communicate)**
>
> MCP vs A2A transport and how responses are structured.

> **[Media products](/dist/docs/3.3.0-beta.0/media-buy/product-discovery/media-products)**
>
> Product structure, `product_card` vs `product_card_detailed`, and rendering.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.