Data Handling in MCP Tool Results: Minimization, Redaction, and Residency

Authorization answers one question: may this caller invoke this tool. A vendor security questionnaire asks a second one, and most MCP servers answer it badly: when the call succeeds, what data leaves the tenant, where does it go, who else sees it, and how long is it kept.

This is not a hypothetical. A tool result is copied into a model's context window, which lives in an inference provider's infrastructure, which is usually not yours and usually not your customer's. The moment a tool returns a row, that row has crossed a boundary the customer's data protection officer cares about. Reviewers have caught up to this. Answer it in the design, not in the questionnaire.

Draw the boundary first

Before writing a tool, write down the path a byte takes. For a typical server it is: your datastore, your MCP server process, the transport, the MCP client (an IDE, a desktop application, an internal agent host), and from there the model provider's API. Sometimes a logging pipeline and an observability vendor branch off the middle.

That path is the answer to "who are your subprocessors", and it is usually longer than the team expects. Two things fall out of drawing it:

  • The MCP client is not under your control. You cannot promise what it does with a result. You can only control what you hand it.
  • Anything in a tool result should be assumed to reach a third-party model provider. If your contract with the customer forbids that for a class of data, the tool must not return that class of data.

Write the path into the threat model as a data-flow diagram. It is the artifact that makes the rest of this credible.

Minimize at the schema, not at the edge

The common shortcut is to return the API's existing response object. Those objects were shaped for a first-party UI that already had the right to show everything. As an MCP result they over-share by default: internal identifiers, audit columns, the full note body when only the subject was needed, a nested customer record hanging off an invoice.

Define the output schema per tool and populate it explicitly. Practical rules:

  • Allow-list fields. Serialize a struct you defined for this tool. Never return json(record). A new column added to the table six months from now should not silently start flowing to a model provider.
  • Prefer identifiers to payloads. If the agent needs to act on a document, return an identifier, a title, and a resource link. Return the body only from a tool whose whole purpose is to read the body.
  • Return the smallest useful page. Default page sizes of ten to twenty, hard maximums, and no unbounded exports. A tool that can return the whole table is a data exfiltration tool with a friendly name.
  • Bound field lengths and truncate with a marker. This limits both leakage and the injection surface, since long free-text fields are where third-party instructions arrive.
  • Classify each field. Tag output fields in the schema as public, internal, personal, or regulated. The tag drives redaction and it becomes the table you paste into the questionnaire.

If a field is regulated and the tool still needs to reason over it, consider returning a derived form: a masked account number, a bucketed date, a boolean instead of a diagnosis.

Redaction is a server function

Redaction that lives in the client, in the prompt, or in a policy document is not redaction. It has to run in the server, on the path between the data layer and the serializer, and it has to be tested.

Run it in one place so there is one thing to review. Deterministic rules first: pattern-based masking for card numbers, national identifiers, and API keys that users have pasted into free-text fields, which they do constantly. Detection is imperfect, so pair it with the field classification above rather than relying on it alone.

The same function has to run on the logging path, and this is where most servers leak. A tool call is logged with its arguments and sometimes its result for debugging; the log ships to an observability vendor; the customer's personal data is now in a third system nobody wrote down. Audit events should carry a redacted summary of inputs, an outcome, and identifiers, never raw payloads. That is a design constraint on the audit schema, and it is one reason to design the event before the first tool.

Retention, and what you can honestly claim

Expect these questions in this order: what do you store, for how long, where, and how is it deleted.

For a well-built MCP server the honest answers are short:

  • Tool results are not stored. They are assembled per call and discarded. Nothing is cached beyond the request unless a specific tool needs it, and if one does, name it.
  • Audit events are stored, with a stated retention period, and they contain metadata and redacted summaries rather than content.
  • Session state is stored for the life of the session, and holds what it needs to hold: session identifier, subject, expiry. Not conversation content.
  • Deletion follows the customer's existing pipeline. If the server holds no copies, a customer's deletion request has nothing to reach into, which is the position you want.

Resist the urge to cache tool results for performance before a customer has asked for it. A cache turns a stateless component into a data store, and a data store changes the answers to the whole section.

Residency and where the server runs

"Where is our data processed" has a clean answer when the server runs in the customer's own cloud account and region, which is how we deploy. The server sits inside the boundary the customer already assessed, the data never transits our infrastructure, and the answer is a region name rather than a paragraph.

It is less clean when the authorization server, a logging sink, or an inference call sits elsewhere. Enumerate those three explicitly. If tokens are issued by a provider in another region, say so. If the agent host calls a model provider outside the customer's region, that is the customer's decision to make, but they can only make it if your documentation names it. Reviewers do not punish an honest list. They punish discovering an unlisted hop.

Say what the agent may never see

Some data should be out of reach of any tool regardless of the caller's permissions: credentials and secrets, other tenants' records, raw security logs, and whatever your customer contract carves out. Enforce that at the data layer, not by leaving the tool out of the catalog, because a future tool will reach the same code path.

A short deny-list in the server, tested, is worth more in a review than a paragraph of intent. Include it in the pre-handover test plan: for each excluded class, an attempt to obtain it through every tool, and the recorded refusal.

The artifact to produce

One page, and it closes most of the data-handling section of a questionnaire:

  1. A data-flow diagram with every hop named, including logging and inference.
  2. A table of tools by output field, with a classification per field.
  3. Redaction rules and where in the code they run.
  4. What is stored, where, for how long, and how it is deleted.
  5. Regions for the server, the authorization server, and the log sink.
  6. The classes of data no tool can reach, and the test that proves it.

None of this is difficult when the tools are designed with the boundary in mind. All of it is expensive to reconstruct afterwards, because the answers depend on how identity and data flow through code that has already been written.

If you would like this page written against a server you already have, that is part of what our MCP security audit produces, and it is a standard deliverable on every server build.