# MCP response controls

Shape tool responses before agents see them.

Everything a tool returns is added to an AI agent's context window. Response controls shape tool responses before agents see them: sensitive data is removed, content that could contain hidden instructions is neutralized and sizes are capped in tokens, per [MCP gateway](https://zato.io/docs/ai/mcp/index.html), on the wizard's step 02, **What do they receive?**.

Shaping happens after the service runs and before the response leaves the gateway - services themselves stay unchanged.

## The minimum size threshold {#the-minimum-size-threshold}

**Min size threshold** - responses smaller than this many tokens skip all shaping and are delivered as they are, so ordinary small responses pay no processing cost. Token counts are estimated, never tokenized - the arithmetic is under [token estimation](https://zato.io/docs/ai/mcp/response-controls.html#token-estimation) below.

## Safeguards {#safeguards}

### Compaction {#compaction}

Null values, redundant whitespace and embedded binary content consume tokens on every call without giving the model any information:

- **Strip nulls** - removes keys whose value is null from objects at every nesting level. Array elements are kept, so positions never shift.
- **Collapse whitespace** - collapses runs of spaces, tabs and line breaks inside string values into a single space.
- **Strip base64** - replaces long base64-encoded strings, such as embedded images or attachments, with a short marker stating the original size.

### PII removal {#pii-removal}

Scans string values for personally identifiable information, such as national identity numbers or IBANs, and replaces each match with a replacement naming the detector - the underlying data never reaches the agent or its model.

- **Lands** select which lands' detectors run, e.g. Spain, Germany or International, and explicit **detectors** picked by name take precedence over lands, with an **exclude** list to keep individual detectors out of an otherwise broad selection.
- **Validate matches** verifies each match with its checksum algorithm before it is replaced, so a number that merely looks like an identifier is left untouched.
- **Stable replacements** give the same value the same numbered replacement throughout one response, so the model can still correlate occurrences of one person or account without ever seeing the value.

How this fits GDPR, HIPAA and similar obligations is covered on the [PII and compliance](https://zato.io/docs/admin/security/pii.html) page.

### Prompt-injection defenses {#prompt-injection-defenses}

Tool responses are untrusted input to the model - these safeguards neutralize the known carriers of hidden instructions:

- **Normalize unicode** - removes zero-width and bidirectional control characters and applies NFC normalization. Such characters can smuggle instructions into otherwise normal text and can split patterns later detection stages need to match.
- **Sanitize markup** - removes script and style elements with their content, event handler attributes and `javascript:` URIs from HTML and Markdown in string values.
- **URL policy** - checks every URL in string values against an allow list of host suffixes, e.g. `example.com` also covers `api.example.com`. Unexpected URLs can exfiltrate data or lure the model into fetching hostile content.

Unicode and markup findings are handled per their mode - **clean** removes them and delivers the response, **reject** refuses the whole response. URLs outside the allow list are handled per the URL mode - **remove** replaces the URL with a marker, **defang** rewrites it so it cannot be followed (`https` becomes `hxxps`, dots become `[.]`) while keeping it visible for analysis, and **reject** refuses the whole response. In [enmasse](https://zato.io/docs/ai/enmasse.html), the Defang mode's attribute value is `neutralize`.

## Token estimation {#token-estimation}

The gateway does not run a tokenizer - it estimates. A response's canonical JSON form is measured in characters and divided by **Characters per token**, rounded up:

```text
tokens = ceil(characters / characters_per_token)
```

A document of 18,340 characters at the default ratio of 4.0 estimates to `ceil(18340 / 4) = 4585` tokens. For content that is mostly non-English text or dense JSON punctuation, tokens span fewer characters and a fractional ratio like 3.5 is closer.

To size a cap against a model's context window, take the share of the window one tool response may use - 5% of a 200,000-token window is a cap of 10,000 tokens.

## Size caps {#size-caps}

**Max response size** caps tool responses in tokens - empty means no cap. Oversized tool responses are the most common cause of exhausted context windows: one unbounded call can push everything the agent received before it out of the window. What happens over the cap is the **size cap mode**:

- **Truncate** degrades the JSON structurally - array tails and longest strings are dropped first, the document stays valid, and an embedded report under the `_truncation` key states what was removed, by JSONPath, so the trimming is visible to the model. In this mode the cap must span at least 4,000 bytes, i.e. 1,000 tokens at 4 characters per token - trimming needs room for both a meaningful payload and the report, so smaller caps are ignored.
- **Block** refuses the response outright, for endpoints where a partial answer would be misleading.

## Refused responses {#refused-responses}

When a safeguard in reject mode or a cap in block mode refuses a response, the agent receives a regular tool result with `isError` set and a text naming the reason, e.g. `Response too large: 2515 tokens, cap is 500` - unlike service exceptions, whose details are never revealed to clients. With the [audit log](https://zato.io/docs/ai/mcp/audit-log.html) on, the recorded size is the size of what the agent actually received, after all shaping.

## Agent filters {#agent-filters}

Filtering is the one control the agent itself drives - **Allow agent filters** adds an optional `response_filter` parameter to every tool, letting an agent pass its own JSONata expression per call. To learn how the expressions work, see [agent filters](https://zato.io/docs/ai/mcp/agent-filters.html).

## See also {#see-also}

- [Agent filters](https://zato.io/docs/ai/mcp/agent-filters.html) - Per-call JSONata expressions agents send themselves
- [Argument validation](https://zato.io/docs/ai/mcp/argument-validation.html) - Checks on tool arguments before the service runs
- [PII and compliance](https://zato.io/docs/admin/security/pii.html) - How PII removal fits GDPR, HIPAA and similar obligations
- [Audit log](https://zato.io/docs/ai/mcp/audit-log.html) - The shaping trace recorded with each event

## Learn more {#learn-more}

- [MCP tutorial](https://zato.io/tutorials/mcp/01.html) - Expose Python services as AI tools and connect Claude Code in minutes
- [AI Integrations overview](https://zato.io/docs/ai/) - Both directions of AI traffic in one platform, and where to start
- [MCP gateway reference](https://zato.io/docs/ai/mcp/) - Configuration, endpoint behavior and the governance controls
- [Tool schemas](https://zato.io/docs/ai/mcp/tool-schemas.html) - How a service's docstring and declared I/O become its tool definition
- [MCP gateway security](https://zato.io/docs/ai/mcp/security.html) - API keys, Basic Auth and bearer tokens for AI agents
- [MCP audit log](https://zato.io/docs/ai/mcp/audit-log.html) - What each agent did, when and with what outcome - payloads never recorded
- [MCP alerts](https://zato.io/docs/ai/mcp/alerts.html) - Failing backends, agents stuck in a loop and gateways with too many tools
- [Cost and limits](https://zato.io/docs/ai/cost-and-limits.html) - Every setting that caps AI cost and traffic, with worked examples
- [GitOps for AI integrations](https://zato.io/docs/ai/enmasse.html) - LLM connections and MCP gateways as YAML in version control
- [AI examples](https://zato.io/docs/ai/examples/) - Complete, runnable scenarios - one real-world need per file
