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
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
| Requirement | Notes |
|---|---|
| Node.js 18+ | The proxy has no runtime dependencies beyond Node built-ins. Runs on macOS, Linux and Windows. |
| Guardion API key | Create one in the console under Settings → API Keys (keys start with grd_). |
| A policy with a PII / Data-Protection detector | DLP 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.
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).
{
"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.
{
"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
| Mode | What it does | Blocks? | Guard endpoints |
|---|---|---|---|
dlp | Reversible 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 |
enforce | Runs 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 |
monitor | Observe 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 values | Redacted — the model only sees tokens | Your own systems: databases, CRMs, internal APIs. Keeps PII out of the LLM, tools keep working. |
untrusted | Redacted — the server never receives PII | Restored — tokens echoed back by the server are resolved | Third-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.
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
| Direction | MCP methods | Content |
|---|---|---|
| Arguments | tools/call, prompts/get | Every string value in params.arguments, at any depth (keys are never modified). |
| Arguments | resources/read | params.uri. |
| Results | tools/call | Text in content[] and structuredContent. |
| Results | resources/read, prompts/get | contents[] / messages[]. |
| Both | Custom / unknown methods | Every string value. |
| Never | Handshake and listing | initialize, 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
| Flag | Default | Description |
|---|---|---|
--mode dlp|enforce|monitor | config mode | Governance mode. Servers wrapped by guardion install mcp always get an explicit --mode (default dlp). |
--trust trusted|untrusted | trusted | Redaction direction (see above). |
--policy <slug> | config policy | Guard policy slug. Required for dlp and enforce. |
--server <name> | inferred | Label 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> | inferred | stdio, 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.
| Variable | Description |
|---|---|
GUARDION_TOKEN | Guardion API key. |
GUARDION_POLICY | Policy slug. |
GUARDION_MODE | dlp, enforce or monitor. |
GUARDION_TRUST | trusted or untrusted. |
GUARDION_API_URL | Guard API base URL. Default https://api.guardion.ai. |
GUARDION_FAIL_CLOSED | true to deny traffic when the Guard API can't be reached. |
GUARDION_SESSION | Fixed session / vault id. Default: a random id per proxy process. |
GUARDION_CONFIG | Path 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.
{
"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
| Symptom | Cause and fix |
|---|---|
| Nothing is redacted, no errors | No 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 monitor | dlp/enforce need a policy. Pass --policy, set GUARDION_POLICY, or set policy in the config file. |
Calls fail with Policy '<slug>' not found | The slug doesn't exist in the account that owns the API key. Check the policy name in the console. |
| A token reaches the server unrestored | The 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
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "[PERSON_NAME_A1B2C3D4], CPF [GOVERNMENT_ID_3F9A1B2C], [CONTACT_77E0C1D2]"
}
]
}
}