MCP DLP

DLP in your own MCP gateway

Call the redact and unredact endpoints from the MCP gateway or agent runtime you already operate, to get the same reversible PII/secret redaction the Guardion plugin provides — without running a local proxy.

If your company already routes MCP traffic through an internal gateway, you don't need the local plugin: add two HTTP calls to the gateway's request/response path. Redact PII replaces sensitive values with tokens and stores the originals in a vault; Restore PII swaps tokens back for the originals. This is exactly what the MCP DLP plugin does internally.

Choosing an integration

OptionBest forWhere it runs
MCP DLP plugin (guardion mcp)Developer machines and desktop agents (Claude Code, Cursor, Claude Desktop, …). Zero code.Next to the MCP host, one proxy per server.
Redact / unredact APIA central MCP gateway, agent platform or backend service. Full control over which fields, when, and for how long.Inside your gateway, one call per direction.
POST /v1/guardRunning the full policy (prompt defense, moderation, PII, …) and redacting in the same call.Anywhere; returns vault_id when it redacts.

Before you start

1. API key. Create a key in the console (Settings → API Keys) and send it as Authorization: Bearer <key>. Keep it server-side.

2. Policy. Both endpoints require a policy slug. The policy's PII / Data-Protection detector decides which entity groups are redacted (e.g. Government ID for CPF/CNPJ, Contact, Payment, Password/Credentials) and at which sensitivity. Use the same policy for redact and unredact — the vault is scoped to it.

3. Session id. Pick a stable id per agent/MCP session (your gateway's session or conversation id). You'll use it as the vault_id.

The flow

Gateway request/response path
agent ──tools/call──▶ [gateway] ──▶ MCP server
                         │  args:   unredact  (trusted server needs real values)
                         │          or redact (external server must not see PII)
                         ▼
                   Guard API
                         ▲
                         │  result: redact   (model must not see PII)
                         │          or unredact (restore values echoed back)
agent ◀──result────── [gateway] ◀── MCP server

For your own MCP servers (databases, CRMs, internal APIs): redact tool results before they reach the model and unredact tool arguments before they reach the server. For external MCP servers invert it: redact arguments, unredact results. The plugin calls this choice trusted / untrusted.

One vault per session

Pass your session id as vault_id on every redact call. The first call creates the vault; later calls append new tokens to it. Unredact then needs just one vault_id, no matter how many tool calls produced the tokens. If you omit vault_id, each redact call mints a new vault and you must track every returned id.

vault_id is only returned when something was actually redacted (it is null when redacted is false).

Lifetime and reuse of tokens

SettingWhereDefaultRecommendation
config.vault_ttlredact3600 sMatch your session length. Tokens that outlive the TTL can no longer be restored and stay as tokens.
one_time_restoreunredacttrueSet false in a gateway: models often repeat a token across several tool calls, and with one-time restore only the first would be restored. Rely on vault_ttl for expiry instead.
config.hash_seedredactnoneSet a per-session seed for consistent pseudonyms: the same value always gets the same token within the seed, so the model can tell that two mentions refer to the same person.
config.thresholdredactpolicyOptional override of the policy's PII sensitivity: 0.9 (L1, fewest flags) … 0.8 (L2, default) … 0.6 (L4, highest recall).

Which fields to send

Both endpoints take a list of chat-style messages and preserve their structure. Put each piece of tool traffic you want scanned in a message — for MCP, one message per string field is the simplest mapping (this is what the plugin does). The endpoints also understand provider formats natively, so you can send an LLM conversation as-is:

ShapeWhat is scanned
{ role, content: "..." }The string.
content: [{ type: "text" | "input_text", text }]Each text part. Images and other parts pass through untouched.
OpenAI tool_calls[].function.argumentsThe JSON arguments string, rewritten in place.
Anthropic tool_use.inputEvery string value (keys are never modified).
Anthropic tool_result.contentThe string or its text blocks.

Failure handling

Treat Guardion like any dependency on the hot path: set a client timeout (the plugin uses 3 s) and decide whether to fail open (forward unmodified) or fail closed (reject the call) when the API is unavailable. For regulated data, fail closed on redact; a failed unredact can safely fall back to forwarding the tokens.

Unredact never errors on tokens it can't resolve (expired, already consumed, unknown vault, other policy): they are left in the text and tokens_restored tells you how many were restored.

Example — Node.js gateway middleware

guardion-dlp.ts
const GUARD = process.env.GUARDION_API_URL ?? "https://api.guardion.ai";
const POLICY = "data-protection";

async function guard<T>(path: string, body: unknown): Promise<T> {
  const res = await fetch(`${GUARD}${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GUARDION_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(3000),
  });
  if (!res.ok) throw new Error(`Guardion ${path} → ${res.status}`);
  return res.json() as Promise<T>;
}

// Tool RESULT from a trusted (internal) MCP server → redact before the model sees it.
export async function redactResult(sessionId: string, text: string): Promise<string> {
  const out = await guard<{ messages: { content: string }[] }>("/v1/pii/redact", {
    policy: POLICY,
    vault_id: sessionId,                        // one vault per session
    config: { vault_ttl: 7200, hash_seed: sessionId },
    messages: [{ role: "tool_response", content: text }],
  });
  return out.messages[0].content;
}

// Tool ARGUMENTS going to a trusted MCP server → restore real values.
export async function restoreArgs(sessionId: string, args: Record<string, unknown>) {
  const out = await guard<{ messages: { content: string }[] }>("/v1/pii/unredact", {
    policy: POLICY,
    vault_id: sessionId,
    one_time_restore: false,                    // the model may reuse a token
    messages: [{ role: "tool_input", content: JSON.stringify(args) }],
  });
  return JSON.parse(out.messages[0].content);
}

Example — Python

guardion_dlp.py
import json, os, requests

GUARD = os.environ.get("GUARDION_API_URL", "https://api.guardion.ai")
HEADERS = {"Authorization": f"Bearer {os.environ['GUARDION_API_KEY']}"}
POLICY = "data-protection"

def redact_result(session_id: str, text: str) -> str:
    r = requests.post(f"{GUARD}/v1/pii/redact", headers=HEADERS, timeout=3, json={
        "policy": POLICY,
        "vault_id": session_id,
        "config": {"vault_ttl": 7200, "hash_seed": session_id},
        "messages": [{"role": "tool_response", "content": text}],
    })
    r.raise_for_status()
    return r.json()["messages"][0]["content"]

def restore_args(session_id: str, args: dict) -> dict:
    r = requests.post(f"{GUARD}/v1/pii/unredact", headers=HEADERS, timeout=3, json={
        "policy": POLICY,
        "vault_id": session_id,
        "one_time_restore": False,
        "messages": [{"role": "tool_input", "content": json.dumps(args)}],
    })
    r.raise_for_status()
    return json.loads(r.json()["messages"][0]["content"])

Tokens contain only [A-Z0-9_] characters, so they survive being embedded in a JSON string as shown above. If you restore arguments field-by-field instead, only send string values that contain a token — they match \[[A-Z0-9_]+_[A-F0-9]{8}\].

Redacting inside a full policy check

If you already call [POST /v1/guard](/docs/api-reference/guard) on each message, a PII detector configured with the redact action returns decision modify, the cleaned content in correction.choices, the applied redaction_spans and a vault_id. Pass that vault_id (and the same policy) to Restore PII. On /v1/guard, the vault defaults to the request's session, so one session shares one vault.

curl https://api.guardion.ai/v1/pii/redact \
  -H "Authorization: Bearer $GUARDION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "policy": "data-protection",
    "vault_id": "sess_42",
    "messages": [
      { "role": "tool_response", "content": "Cliente Maria Souza, CPF 419.815.118-06" }
    ]
  }'
Response
{
  "messages": [
    {
      "role": "tool_response",
      "content": "Cliente [PERSON_NAME_A1B2C3D4], CPF [GOVERNMENT_ID_3F9A1B2C]"
    }
  ],
  "vault_id": "sess_42",
  "redacted": true,
  "entities_found": 2,
  "spans": [
    { "index": 0, "start": 8, "end": 19, "label": "PERSON_NAME", "token": "[PERSON_NAME_A1B2C3D4]" },
    { "index": 0, "start": 25, "end": 39, "label": "GOVERNMENT_ID", "token": "[GOVERNMENT_ID_3F9A1B2C]" }
  ]
}