Every MCP tool result goes somewhere you don’t fully control: into the model’s context window. When your AI calls crm__get_contact, the customer’s name, email, phone number and notes land in the prompt. From there they can be echoed back to the user, summarised, forwarded into the next tool call, or retained by whoever runs the model. That is the whole reason “PII filtering” has become a headline feature on MCP gateways.

This post is about what that filtering can realistically do, where it quietly fails, and why the first control you should reach for is not a filter at all.

Where personal data actually flows in an MCP call

A single tool call touches personal data at four points. It helps to name them, because most “PII protection” claims cover only one.

  1. The arguments. What the model sends to the server. A search query containing a patient name, an email address pasted into a ticket body.
  2. The result. What the server sends back. A contact record, a transcript, a database row.
  3. The model context. The result becomes part of the conversation and stays there for every later turn, visible to every later tool call.
  4. The next call. The model may copy fields from one result into the arguments of the next tool. Read from the CRM, write into Slack.

Request-side filtering covers point 1. Response-side filtering covers point 2. Nothing in the protocol covers 3 and 4, which is where the interesting leaks happen.

What a response filter can catch

A gateway that sits between the client and the server sees the raw tool result before the model does. It can run pattern matching and entity recognition over it and replace matches with placeholders: [EMAIL], [PHONE], [IBAN]. For well-structured identifiers this works well:

  • Email addresses, phone numbers, IBANs, credit card numbers, national ID formats, IP addresses.
  • Known field names in structured JSON (ssn, date_of_birth, diagnosis) when the server returns predictable shapes.
  • Terms from a custom dictionary: your customer list, employee names, internal project codenames.

The model still gets a useful result. “Contact [NAME] at [EMAIL] about the renewal” is enough to draft the email and nothing of value has crossed the boundary.

What it can’t catch

Three failure modes show up in practice, and vendors rarely put them on the landing page.

Context, not pattern. “The CEO’s daughter was admitted on Tuesday” contains no regex-matchable token and is still health data about an identifiable person. Named-entity models catch some of this. They miss plenty, especially in German, Turkish or any language the model was not tuned for.

The model reassembles it. If the filter redacts the email but leaves the name and the company, the model can often infer the email anyway. Redaction that is inconsistent across fields is close to no redaction at all.

The filter breaks the tool. Redact the id field of a record and the model can no longer update it. Redact a phone number and the “send SMS” tool fails. Teams respond by loosening the filter until it passes everything that matters, at which point it mostly protects against the easy cases.

None of this means filtering is useless. It means filtering is a second layer, and you should not let it stand in for the first.

The first control: don’t expose the tool

The cheapest way to keep personal data out of the model is to never call the tool that returns it. Most MCP servers expose far more than a given workflow needs. A support assistant that answers “what is this ticket about?” does not need list_all_customers. A reporting agent that counts deals does not need get_contact_details.

Per-tool allow/ask/block rules are a blunt instrument, and that is their strength. Blocked tools cannot leak, regardless of language, format, or how clever the injected prompt is. It is the MCP equivalent of least privilege: start from the tools a role actually needs and add the rest only with a reason.

A good practical split is to allow read tools whose results are aggregate or non-personal, put tools that return individual records behind an approval step, and block bulk-export tools outright for AI clients. The data the model never sees is the data you never have to filter, log, or explain to a regulator.

The second control: separate reads from sends

The dangerous combination is a tool that reads personal data plus a tool that can send content outside the organisation, both reachable in the same session. That is the shape of every MCP exfiltration story so far. If a client has crm__get_contact and http__fetch at the same time, a single injected instruction in a web page is enough to move a record out.

Filtering tries to make the read harmless. Policy makes the send impossible. Splitting sensitive-read servers and outbound-capable servers across different clients or different roles is more reliable than any regex.

Then filter, and log that you did

Once the tool surface is minimal, response filtering earns its place. It reduces what an allowed read exposes, and it gives you something to show: the log entry that says “result returned with 3 fields redacted” is exactly what a data protection officer wants to see next to “Claude read contact 4411 at 14:03”.

Keep the filter’s own output out of the log. A redaction log that stores the original values has recreated the problem one layer down.

How gate approaches this: gate enforces on the request side first. Every tool on every connected server gets an allow, ask, or block rule, rules can differ per team and per client, and every call is logged with a plain-language summary. That means you can keep record-level tools off the table for the roles that don’t need them before a single result reaches the model. Response-side redaction is on the roadmap, not in the product today. We would rather say that plainly than sell a regex as a guarantee.

A checklist

  1. List which connected tools return personal data at the record level. The scanner flags descriptions that reference credentials or sending data elsewhere; which tools return personal data is a judgement you make from the tool list.
  2. Block those tools for every role that does not need them. Put the rest behind approval.
  3. Make sure no single client holds both a sensitive-read tool and an outbound-send tool.
  4. Add response filtering for the reads you kept. Test it in your users’ languages, not just English.
  5. Log the redaction event, never the redacted value.
  6. Check what the model provider retains. A zero-retention agreement is worth more than a filter.

Filtering is a useful seatbelt. The tool surface is the speed limit. Set the limit first.