> ## 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.

# MCP Response Extraction

> How to extract AdCP success response data from MCP tool results: structuredContent, text fallback, and client implementation requirements.

This page defines the normative algorithm for extracting AdCP success response data from MCP tool results. For error extraction, see [Transport Error Mapping](/dist/docs/3.3.0-beta.0/building/operating/transport-errors).

## Layer Separation

| Path | When | Data Source |
| - | - | - |
| Success extraction (this page) | `isError` absent or `false` | `structuredContent` or `content[].text` |
| Error extraction ([transport-errors](/dist/docs/3.3.0-beta.0/building/operating/transport-errors)) | `isError: true` | `structuredContent.adcp_error` or text fallback |

Clients MUST check `isError` before deciding which extraction path to use. A response with `isError: true` MUST NOT be processed as a success response, even if it contains `structuredContent` with non-error data.

## Extraction Algorithm

Clients MUST extract AdCP data from MCP tool results in this order:

1. **Guard: reject error responses.** If `isError` is truthy, return null. Error extraction is a separate path.
2. **`structuredContent`** — If present and is a non-array object, return it. If the only key is `adcp_error`, return null (this is an error response missing the `isError` flag).
3. **Text fallback** — Iterate `content[]` items in array order. For each item where `type === 'text'`, enforce the configured response-size limit, then attempt `JSON.parse`. If the result is a non-array object, return it. Skip items that fail to parse, parse as non-objects, or contain only an `adcp_error` key.
4. **No structured data found** — Return null. The response is plain text with no machine-readable AdCP data.

```javascript MCP Client theme={null}
const MAX_RESPONSE_BYTES = 4 * 1024 * 1024; // 4 MiB default; configurable per agent and per call

function extractAdcpResponseFromMcp(response) {
  // 1. Error responses go through transport-errors extraction
  if (response.isError) return null;

  // 2. structuredContent (preferred — MCP 2025-03-26+)
  if (response.structuredContent != null
      && typeof response.structuredContent === 'object'
      && !Array.isArray(response.structuredContent)) {
    const sc = response.structuredContent;
    // adcp_error-only structuredContent is an error missing isError flag
    const keys = Object.keys(sc);
    if (keys.length === 1 && keys[0] === 'adcp_error') return null;
    return sc;
  }

  // 3. Text fallback — JSON.parse content[].text
  if (response.content && Array.isArray(response.content)) {
    for (const item of response.content) {
      if (item.type === 'text' && item.text) {
        if (item.text.length > MAX_RESPONSE_BYTES) continue; // same limit as the whole message
        try {
          const parsed = JSON.parse(item.text);
          if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
            // Skip adcp_error-only payloads (error missing isError flag)
            const keys = Object.keys(parsed);
            if (keys.length === 1 && keys[0] === 'adcp_error') continue;
            return parsed;
          }
        } catch { /* not JSON */ }
      }
    }
  }

  return null;
}
```

## Extraction Paths

### structuredContent (Preferred)

MCP 2025-03-26 introduced `structuredContent` for typed tool results. AdCP servers return the full response payload here:

```json theme={null}
{
  "content": [{"type": "text", "text": "Found 3 products matching your brief."}],
  "structuredContent": {
    "status": "completed",
    "message": "Found 3 products",
    "products": [
      {"product_id": "ctv_sports_premium", "name": "Premium Sports CTV"},
      {"product_id": "ctv_news_standard", "name": "Standard News CTV"}
    ]
  }
}
```

The `structuredContent` object IS the AdCP response — task-specific fields (`products`, `media_buy_id`, `status`, etc.) are at the top level, not nested.

When `structuredContent` carries the authoritative success payload, `content` MAY contain a terse human-readable summary. Producers SHOULD NOT serialize the complete AdCP payload again into `content[].text`; doing so doubles the wire bytes and model-context cost without adding information. A server supporting an older MCP client that cannot read `structuredContent` MAY instead put the JSON payload in text as the fallback form below and omit `structuredContent`. It should not emit both full copies.

### Text Fallback

Older MCP servers (pre-2025-03-26) serialize the response as JSON in `content[].text`:

```json theme={null}
{
  "content": [
    {"type": "text", "text": "{\"status\":\"completed\",\"products\":[{\"product_id\":\"ctv_premium\"}]}"}
  ]
}
```

Clients parse the first text item that yields a JSON object. When both `structuredContent` and text JSON exist, `structuredContent` takes precedence.

## Relationship to Error Extraction

Success and error extraction are complementary:

```javascript theme={null}
function handleMcpResponse(response) {
  // Try error extraction first (only runs if isError is true)
  const error = extractAdcpErrorFromMcp(response);
  if (error) return handleError(error);

  // Then try success extraction
  const data = extractAdcpResponseFromMcp(response);
  if (data) return handleSuccess(data);

  // Plain text response — no structured data
  return handlePlainText(response.content);
}
```

## Security Considerations

### Seller-Controlled Data

All data in `structuredContent` and `content[].text` is seller-controlled. The same prompt injection and data boundary requirements from [Transport Error Mapping](/dist/docs/3.3.0-beta.0/building/operating/transport-errors#security-considerations) apply.

### Size Limits

For pageable reads and other read-only tasks that support field projection, producers SHOULD target at most 128 KiB per serialized MCP response; see [Managing response size](/dist/docs/3.3.0-beta.0/building/concepts/managing-response-size#interoperability-size-target). Clients SHOULD independently enforce a streamed limit on the complete serialized MCP message before buffering or JSON decoding. The recommended client safety limit is 4 MiB (4,194,304 bytes). For text fallback, also check the `content[].text` size before the inner `JSON.parse`, against that same limit rather than a tighter one — for a server that returns only `content[].text`, that item is the entire response, so a sub-limit figure there would silently become the effective cap. The larger safety limit is not a producer target: it bounds hostile or buggy input after normal interoperability guidance has failed.

The streamed limit is the MCP 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): count bytes as they arrive and abort rather than buffer-then-check, pre-reject an over-limit `Content-Length`, bound raw and decoded bytes, pair the cap with a deadline, bound JSON depth and node count after the cap passes, and fail with a distinct error instead of truncating. It applies to every MCP response the client reads — `tools/call`, `tools/list`, `initialize`, and the transport-native `tasks/get` and `tasks/result` results that carry an async `CallToolResult`. Leaving the async retrieval path unbounded is the same exhaustion with one extra hop.

A Streamable HTTP server MAY answer a POST with a single `application/json` body or with an SSE stream (`text/event-stream`), and the bound applies to both — a server does not escape it by choosing SSE. On the SSE path it applies to each event and to the cumulative bytes the client retains for one request, carried across `Last-Event-ID` resumption rather than reset by it, so an oversized result cannot be split across many frames or across a reconnect. On a non-HTTP transport (stdio) there is no `Content-Length` to pre-reject; the streaming count and the per-message bound still apply.

Clients SHOULD make the limit configurable per agent and per call. 4 MiB is the MCP default and is also the protocol-wide ceiling on a default, so a client raises the limit for a specific agent or a specific call rather than shipping a larger default. Implementations are free to default lower; the 4 MiB figure bounds hostile or buggy input after normal interoperability guidance has failed, and is not a size a producer may aim at.

### Prototype Pollution

Clients MUST NOT merge extracted response objects into application state via `Object.assign` or spread without filtering keys. Seller-controlled keys like `__proto__` or `constructor` can trigger prototype pollution. Validate against the expected task response schema before merging.

### Type Confusion

Clients MUST check `isError` before success extraction. Without this guard, a client could process an error response as success data, leading to incorrect business logic (e.g., treating a `RATE_LIMITED` error as product data).

## Client Library Requirements

Client libraries that implement this spec MUST:

1. **Check `isError` before extraction.** Return null for error responses.
2. **Prefer `structuredContent`.** Only fall back to text parsing when `structuredContent` is absent.
3. **Validate parsed text.** Only accept non-array objects from `JSON.parse`. Reject arrays, strings, numbers, booleans, and null.
4. **Handle `adcp_error`-only `structuredContent`.** When `structuredContent` contains only an `adcp_error` key, return null — this is an error response that may be missing the `isError` flag.
5. **Do not require duplicated success data.** Treat summary-only `content[].text` as normal whenever `structuredContent` is present; never compare it with, or expect it to reproduce, the structured payload.

## Test Vectors

Machine-readable test vectors are available at [`/static/test-vectors/mcp-response-extraction.json`](https://adcontextprotocol.org/test-vectors/mcp-response-extraction.json). Each vector contains:

* `path`: extraction path (`structuredContent` or `text_fallback`)
* `response`: the MCP tool result envelope
* `expected_data`: the AdCP data that should be extracted (or `null`)

Client libraries SHOULD validate their extraction logic against these vectors.

## See Also

* [Transport Error Mapping](/dist/docs/3.3.0-beta.0/building/operating/transport-errors) — error extraction from MCP and A2A
* [A2A Response Extraction](/dist/docs/3.3.0-beta.0/building/by-layer/L0/a2a-response-extraction) — equivalent spec for A2A
* [MCP Guide](/dist/docs/3.3.0-beta.0/building/by-layer/L0/mcp-guide) — MCP transport integration


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