MCP DLP

MCP DLP with the Guardion plugin

Wrap any MCP server with the @guardion/guardion plugin to redact PII and secrets on every tool call — reversibly — so the model only ever sees placeholder tokens while your tools keep working on real data.

MCP DLP is a local proxy shipped in the @guardion/guardion npm package. Your MCP host (Claude Code, Claude Desktop, Cursor, Windsurf, Cline, VS Code, ChatGPT Desktop, or any stdio MCP client) launches guardion mcp instead of the MCP server; the proxy starts (or connects to) the real server and relays every JSON-RPC message in both directions, scanning and rewriting it in flight.

Detection is done by the Guard API using your policy — the plugin never re-implements detection. What gets redacted (names, contacts, CPF/CNPJ and other government IDs, payment data, secrets, …) is configured once in the Guardion console and applies to every wrapped server.

Prefer to integrate DLP into your own MCP gateway instead of running a local proxy? The plugin is a thin client over two public endpoints — see DLP in your own MCP gateway.

How it works

Data flow
MCP host  ⇄  guardion mcp (proxy)  ⇄  MCP server (local or remote)
                    │
                    └─ Guard API  (/v1/pii/redact · /v1/pii/unredact · /v1/guard)

When a sensitive value is found, it is replaced with a token such as [GOVERNMENT_ID_3F9A1B2C] and the original is kept in a short-lived, account- and policy-scoped vault. When a token later travels back in the other direction, the proxy restores the original. The model reasons over tokens; the tool receives real values.

Requirements

RequirementNotes
Node.js 18+The proxy has no runtime dependencies beyond Node built-ins. Runs on macOS, Linux and Windows.
Guardion API keyCreate one in the console under Settings → API Keys (keys start with grd_).
A policy with a PII / Data-Protection detectorDLP and enforce modes require a policy slug. The detector's checks (e.g. Government ID, Contact, Payment, Password) decide what is redacted.

Quick start

Install the CLI, run the guided setup, then wrap every MCP server the plugin finds in your local MCP config files.

Install and wrap your MCP servers
npm install -g @guardion/guardion      # or run any command with: npx -y @guardion/guardion <cmd>

guardion init                          # choose "MCP servers — DLP", paste your key, pick a policy
guardion token test                    # verifies the key against the Guard API

guardion scan --mode mcp --dry-run     # preview which servers will be wrapped
guardion install mcp --policy data-protection   # wrap them (mode defaults to dlp)

guardion scan --mode mcp --revert      # undo: restores the original config

guardion install mcp rewrites the MCP config files it detects — Claude Desktop, Claude Code (~/.claude.json), Cursor (~/.cursor/mcp.json), Windsurf, Cline, ChatGPT Desktop, VS Code (.vscode/mcp.json) and a project .mcp.json. A backup is written next to each file as <file>.guardion.bak, and the rewrite is idempotent. Restart your MCP host after installing.

Wrap a server manually

You can also edit an MCP config yourself: put npx -y @guardion/guardion mcp <flags> -- in front of the original command. Passing the key and policy through the server's env block is the most portable setup and is recommended on macOS and Windows (see Troubleshooting).

.mcp.json — local (stdio) server
{
  "mcpServers": {
    "customers-db": {
      "command": "npx",
      "args": [
        "-y", "@guardion/guardion", "mcp",
        "--mode", "dlp",
        "--trust", "trusted",
        "--server", "customers-db",
        "--policy", "data-protection",
        "--", "node", "./servers/customers-db.js"
      ],
      "env": {
        "GUARDION_TOKEN": "grd_...",
        "GUARDION_POLICY": "data-protection"
      }
    }
  }
}

Remote servers and internal MCP gateways

Remote MCP servers — including an MCP gateway you run internally — are wrapped with --url. Forward any auth the remote needs with one or more --header flags. The transport is inferred (http_forward for Streamable HTTP, sse_bridge for SSE URLs) and can be forced with --transport.

.mcp.json — remote (HTTP) server
{
  "mcpServers": {
    "internal-gateway": {
      "command": "npx",
      "args": [
        "-y", "@guardion/guardion", "mcp",
        "--mode", "dlp",
        "--trust", "untrusted",
        "--url", "https://mcp.internal.example.com/mcp",
        "--header", "Authorization: Bearer <gateway-token>"
      ],
      "env": {
        "GUARDION_TOKEN": "grd_...",
        "GUARDION_POLICY": "data-protection"
      }
    }
  }
}

To expose the proxy itself as an HTTP MCP endpoint (for hosts that only speak HTTP), add --listen 8900 (binds 127.0.0.1 by default; host:port is accepted). Combined with --url this becomes a local reverse proxy in front of a remote server.

Modes

ModeWhat it doesBlocks?Guard endpoints
dlpReversible redaction of tool arguments and results. Traffic always continues.No (only when fail_closed is on and Guard is unreachable)/v1/pii/redact, /v1/pii/unredact
enforceRuns every guardrail in the policy (prompt defense, moderation, PII, …). Blocks tool calls the policy denies, redacts where the policy says redact, and checks tool definitions for poisoning and rug-pulls.Yes, on a policy deny/v1/guard, /v1/pii/unredact
monitorObserve only: every call is evaluated and logged asynchronously. Nothing is modified.Never/v1/guard

A blocked call returns a JSON-RPC error to the host: { "code": -32000, "message": "[guardion] blocked — Guardion: <LABEL>" }. Every evaluation (all modes) appears in the console under Observability, tagged with the server and tool name.

Trust direction

--trust decides which side of the proxy is allowed to see real values. Pick it per server.

`--trust`Tool arguments (host → server)Tool results (server → host)Use it for
trusted (default)Restored — the server receives real valuesRedacted — the model only sees tokensYour own systems: databases, CRMs, internal APIs. Keeps PII out of the LLM, tools keep working.
untrustedRedacted — the server never receives PIIRestored — tokens echoed back by the server are resolvedThird-party or external MCP servers. Keeps PII from leaving your boundary.

Worked example (trusted server)

A support agent looks up a customer, then updates their record. The model never sees the real CPF or email, but the database gets the real values.

Round trip through the proxy
1. tools/call  get_customer { "id": "8812" }
2. server result   → "Maria Souza, CPF 419.815.118-06, maria@example.com"
3. model receives  → "[PERSON_NAME_A1B2C3D4], CPF [GOVERNMENT_ID_3F9A1B2C], [CONTACT_77E0C1D2]"
4. tools/call  update_customer { "cpf": "[GOVERNMENT_ID_3F9A1B2C]", "status": "active" }
5. server receives → { "cpf": "419.815.118-06", "status": "active" }

The session vault

Each proxy process holds one vault per session: the first redaction mints it and every later redact/restore reuses it, so tokens stay resolvable across tool calls for the lifetime of the session. Set GUARDION_SESSION to pin the session id yourself (for example, to correlate with your own agent session in Observability).

Tokens expire after the vault TTL — 1 hour by default. Set vault_ttl (seconds) in ~/.guardion/config.json to change it. Restarting the MCP host starts a new proxy process and therefore a new session and vault.

Restores are one-time by default: once a token has been restored, it is consumed. If the model reuses the same token in a later call, it reaches the server as the literal token. Design tool flows so each sensitive value is sent back once, or integrate the API directly and disable one-time restore (see DLP in your own MCP gateway).

What is scanned

DirectionMCP methodsContent
Argumentstools/call, prompts/getEvery string value in params.arguments, at any depth (keys are never modified).
Argumentsresources/readparams.uri.
Resultstools/callText in content[] and structuredContent.
Resultsresources/read, prompts/getcontents[] / messages[].
BothCustom / unknown methodsEvery string value.
NeverHandshake and listinginitialize, ping, tools/list, resources/list, prompts/list, notifications, …

Binary payloads (data, blob) and metadata (mimeType, annotations, _meta, …) are skipped. Large integers (9+ digits — e.g. an unformatted CPF stored as a number) are scanned as text. Redacted values are written back in place, so the JSON structure the host and server expect is preserved.

CLI flags

FlagDefaultDescription
--mode dlp|enforce|monitorconfig modeGovernance mode. Servers wrapped by guardion install mcp always get an explicit --mode (default dlp).
--trust trusted|untrustedtrustedRedaction direction (see above).
--policy <slug>config policyGuard policy slug. Required for dlp and enforce.
--server <name>inferredLabel shown in findings (e.g. npx -y @scope/server-x → server-x; a URL → its host).
--url <url>—Remote MCP endpoint to wrap.
--header 'K: V'—Header forwarded to the remote server. Repeatable.
--transport <kind>inferredstdio, http_forward, http_input, http_reverse or sse_bridge.
--listen <port|host:port>—Expose the proxy as an HTTP MCP endpoint.
-- <command …>—The original server command (stdio servers).

Environment variables

Environment variables override ~/.guardion/config.json; CLI flags override both.

VariableDescription
GUARDION_TOKENGuardion API key.
GUARDION_POLICYPolicy slug.
GUARDION_MODEdlp, enforce or monitor.
GUARDION_TRUSTtrusted or untrusted.
GUARDION_API_URLGuard API base URL. Default https://api.guardion.ai.
GUARDION_FAIL_CLOSEDtrue to deny traffic when the Guard API can't be reached.
GUARDION_SESSIONFixed session / vault id. Default: a random id per proxy process.
GUARDION_CONFIGPath to an alternate config file.

Config file

guardion init writes ~/.guardion/config.json. Admins can ship a machine-wide config at /etc/guardion/config.json (%ProgramData%\Guardion\config.json on Windows); user settings override it unless it sets "managed": true.

~/.guardion/config.json
{
  "version": 1,
  "api_url": "https://api.guardion.ai",
  "policy": "data-protection",
  "application": "mcp",
  "mode": "dlp",
  "trust": "trusted",
  "fail_closed": false,
  "vault_ttl": 3600,
  "hooks": { "timeout_ms": 3000 }
}

Failure behaviour

The proxy is fail-open by default: if the Guard API times out (default 3 s, hooks.timeout_ms), is unreachable or returns an error, the message is forwarded unmodified and your agent keeps working. For regulated data flows, set fail_closed: true (or GUARDION_FAIL_CLOSED=true) — a failed redaction then blocks the call instead of letting raw data through. A failed restore never blocks.

Built-in agent tools (Claude Code)

MCP DLP covers MCP tools. To also mask sensitive output from Claude Code's built-in tools (Read, Bash, WebFetch, Grep, …), install the hooks: guardion claude-code --mode hooks --redact (add --enforce to block on a policy deny). Hook redaction is one-way masking of tool output; MCP tool output is left to the proxy. See CLI agent hooks.

Troubleshooting

SymptomCause and fix
Nothing is redacted, no errorsNo API key reached the proxy, so every call fails open. On macOS and Windows, guardion init stores the key in the OS keychain, which the proxy does not read — set GUARDION_TOKEN in each server's env block (or in ~/.guardion/token).
Log says no policy resolved … falling back to monitordlp/enforce need a policy. Pass --policy, set GUARDION_POLICY, or set policy in the config file.
Calls fail with Policy '<slug>' not foundThe slug doesn't exist in the account that owns the API key. Check the policy name in the console.
A token reaches the server unrestoredThe vault expired (vault_ttl), the token was already restored once, or the host restarted the server (new session).
Where are the logs?The proxy logs to stderr with the prefix [guardion] mcp-interpose: — visible in your MCP host's server logs.
npx -y @guardion/guardion mcp \
  --mode dlp --trust trusted \
  --policy data-protection \
  -- npx -y @modelcontextprotocol/server-filesystem ./data
Response
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[PERSON_NAME_A1B2C3D4], CPF [GOVERNMENT_ID_3F9A1B2C], [CONTACT_77E0C1D2]"
      }
    ]
  }
}