Skip to main content
Glama
Rinava

phi-redact-mcp

by Rinava

umbryn-mcp

An MCP server that redacts PII/PHI from text before it ever reaches an LLM — self-hosted, fail-closed, and HIPAA-aware.

PyPI version Tests Python versions License: MIT Ruff PRs welcome

Teams building LLM and agent pipelines in regulated domains have no clean, drop-in way to strip PHI/PII from a payload before it crosses into a model provider's infrastructure. umbryn-mcp is that boundary: three MCP tools — redact, restore, detect — that scrub sensitive values into reversible placeholders, run entirely inside infrastructure you control, and block the request if detection is uncertain instead of leaking data.

redact("Patient MRN: 1234567, provider NPI 1234567893, ssn 078-05-1120, john.doe@example.com")

  redacted_text  (safe to send to the model):
    "Patient MRN: [MEDICAL_RECORD_NUMBER_1], provider NPI [NPI_1], ssn [US_SSN_1], [EMAIL_ADDRESS_1]"

  token_map      (kept local, never sent to the model):
    [MEDICAL_RECORD_NUMBER_1] → 1234567
    [NPI_1]                   → 1234567893
    [US_SSN_1]                → 078-05-1120
    [EMAIL_ADDRESS_1]         → john.doe@example.com

Send the redacted text to the model; keep the token_map local; call restore afterward to rehydrate the result. Round-trips are byte-exact and proven with property-based tests.


Why this exists

The PHI/PII-redaction MCP niche is real but underserved — the existing options are thin Presidio wrappers with no HIPAA-specific detection and, critically, no guarantee that a detection failure blocks the request instead of silently passing raw data through. So teams either roll their own boundary or ship sensitive data to a provider and lean on a BAA to cover it — the design-time mistake that causes real compliance incidents.

Naive Presidio wrapper

Regex-in-your-app

Cloud DLP API

umbryn-mcp

Drop-in MCP tools

sometimes

Fail-closed on uncertain detection

HIPAA identifiers (NPI, DEA, MBI, MRN, CLIA)

partial

partial

Reversible (restore original)

rarely

DIY

some

Runs self-hosted, zero egress

❌ (sends data out)

Works with zero heavy deps

❌ (needs spaCy)

n/a

✅ (regex engine)

Optional ML NER (names, addresses)

✅ ([presidio] extra)

Why it was built: MCP went mainstream fast — it's now first-class in Claude, Cursor, and ChatGPT, across thousands of servers — but the PHI/PII-redaction corner was left to a few unmaintained wrappers. This fills that gap with a single honest, auditable, fail-closed boundary, kept open source so the redaction logic you depend on is fully inspectable rather than a black box.

Related MCP server: pii-anonymizer

Features

  • Three tools, one boundaryredact (→ scrubbed text + reversible token map), restore (→ original), detect (→ entities found, no mutation).

  • Fail-closed by construction — if detection errors or any detection lands below the confidence threshold, the call returns a typed error. Uncertainty blocks; it never redacts-what-it-can and passes the rest.

  • HIPAA-aware detection — checksum-validated NPI and DEA, position-typed Medicare MBI, context-anchored MRN, CLIA lab IDs, plus standard PII (email, phone, SSN, credit card, IBAN, IP, URL).

  • Zero-egress, self-hosted — the default engine is pure regex + checksums with no network calls and no heavy dependencies. It installs anywhere Python does.

  • Optional ML upgradepip install "umbryn-mcp[presidio]" adds Microsoft Presidio + spaCy for PERSON/LOCATION NER, transparently.

  • Reversible & deterministic — collision-proof typed placeholders make restore(redact(x)) == x for arbitrary input; same input + config always yields the same output.

When to use it (and when not to)

Reach for umbryn-mcp when:

  • You send healthcare, clinical, financial, or user-generated text to a third-party LLM API and need PHI/PII kept out of that provider's infrastructure and logs.

  • You're building an agent or MCP pipeline in a regulated domain and want a drop-in scrubbing boundary you wire in with one tool call.

  • You need reversible redaction so downstream steps still work: redact → send to model → restore.

  • You want a self-hosted, no-egress detector you can audit line by line.

  • You need HIPAA-specific identifiers (NPI, DEA, Medicare MBI, MRN, CLIA), not just names and emails.

Reach for something else when:

  • You need irreversible de-identification / anonymization (tokenization, k-anonymity) — redaction here is reversible by design.

  • You need to redact non-text data (images, audio, PDFs, database rows) — scope is text.

  • You want a certified compliance product — this is one technical control, not a compliance program (see Scope & honest limitations).

  • You want a transparent proxy that auto-scrubs everything in the request path — v1 is explicit tool calls; proxy mode is on the roadmap.

  • You require guaranteed 100% recall — no detector, this one included, can promise that.

Quickstart (< 60 seconds)

pip install umbryn-mcp        # zero heavy deps; runs immediately

Then register it with your MCP client.

Claude Desktop / Claude Code (claude_desktop_config.json, or claude mcp add umbryn-mcp -- umbryn-mcp):

{
  "mcpServers": {
    "umbryn-mcp": {
      "command": "umbryn-mcp"
    }
  }
}

Cursor (.cursor/mcp.json) and VS Code use the same shape — see examples/ for ready-to-paste configs.

Want name/address detection too?

pip install "umbryn-mcp[presidio]"
python -m spacy download en_core_web_lg

The server auto-detects Presidio and upgrades — no config change needed. (Set UMBRYN_ENGINE=regex to force the dependency-free engine, or =presidio to require the ML one.)

How it works

A tool call comes in over stdio; the Redactor core runs the configured detection engine, resolves overlaps deterministically, applies the fail-closed threshold check, and swaps detected spans for reversible typed placeholders. Only scrubbed text is meant to leave the boundary you run.

flowchart LR
    A[MCP client<br/>Claude · Cursor · agent] -- redact / restore / detect --> B[umbryn-mcp<br/>stdio server]
    B --> C[Redactor core<br/>fail-closed · reversible]
    C --> D{Detection engine}
    D -->|default, zero deps| E[Regex + checksums]
    D -->|optional| F[Presidio + spaCy NER]
    C -. scrubbed text .-> A
    A -- scrubbed text only --> G[(LLM / downstream)]

The Redactor core depends only on a small DetectionEngine interface — never on Presidio or MCP directly. Raw data and the detection engine stay inside the boundary you run; only scrubbed text leaves it. See docs/ARCHITECTURE.md and docs/THREAT_MODEL.md.

The tools

redact(text) → { redacted_text, token_map, entities }

Replaces detected PHI/PII with typed placeholders like [NPI_1]. token_map maps each placeholder back to its original value — keep it local; never send it to the model. entities lists what was redacted (type/span/score) for auditing.

restore(redacted_text, token_map) → { text }

Reverses a redaction, recovering the original text exactly. Safe to call on model output that still contains the placeholders.

detect(text) → { entities, count }

Reports the entities found — type, span, confidence — without modifying the text. Unlike redact, it surfaces low-confidence hits rather than blocking, so you can inspect coverage before trusting the boundary in a pipeline.

How to use it (a real pipeline)

The pattern is redact → model → restore, with the token map never leaving your side:

  1. Scrub before the model. Call redact(user_text). Send only redacted_text to the LLM. Keep token_map in your process — treat it as sensitively as the raw input, and never pass it to the model.

  2. Let the model work on placeholders. It sees [NPI_1], [US_SSN_1], etc. — semantically neutral tokens it can reason about and echo back.

  3. Rehydrate after. Call restore(model_output, token_map) to swap the real values back into the model's response before it reaches your user or database.

  4. Handle the block. If redact returns a [LOW_CONFIDENCE] or [DETECTION_ERROR] tool error, the boundary refused to leak — surface it, tighten input, or lower the risk, but don't send the raw text onward.

Before trusting it in a pipeline, call detect(sample_text) on representative (synthetic) data to see exactly what is and isn't caught, and tune the thresholds (below) to your risk tolerance.

Fail-closed, precisely

Two thresholds govern every redact call:

  • detection_floor (default 0.35) — the sensitivity boundary. Signals below it are treated as noise.

  • min_confidence (default 0.5) — the trust threshold.

Any candidate that survives the floor but scores below min_confidence puts the call into fail-closed mode: it returns a [LOW_CONFIDENCE] error rather than redacting the confident spans and passing the uncertain one through. Engine errors return [DETECTION_ERROR]. On any error, no redacted text is returned. Both thresholds are configurable (see below).

Configuration

All optional; sane defaults mean it runs with zero config. Set via the client's env block.

Variable

Default

Meaning

UMBRYN_ENGINE

auto

auto (Presidio if installed, else regex), regex, or presidio

UMBRYN_MIN_CONFIDENCE

0.5

Trust threshold; detections below it fail closed

UMBRYN_DETECTION_FLOOR

0.35

Below this, a signal is treated as noise

UMBRYN_MAX_INPUT_CHARS

100000

Reject larger input with a typed error

UMBRYN_SPACY_MODEL

en_core_web_lg

spaCy model for the Presidio engine

UMBRYN_AUDIT_LOG

false

Emit a structured audit record per redact call (counts and types only)

UMBRYN_CONFIG

(unset)

Path to a JSON config file (below)

Config file

For settings that don't fit a flat environment variable, point UMBRYN_CONFIG at a JSON file. Environment variables still win over the file for the scalar values above, so you can ship one file and tweak per launch. A malformed file (bad JSON, unknown threshold, un-compilable regex) fails closed at startup rather than degrading silently.

{
  // Per-entity trust thresholds override min_confidence for that type.
  "entity_thresholds": { "PHONE_NUMBER": 0.7, "IP_ADDRESS": 0.9 },

  // Entity types to drop entirely — never detected, never redacted.
  // (A privacy trade-off you're opting into: a disabled type can leak.)
  "disabled_entities": ["URL"],

  // Your own recognizers, no fork required. `validator` names a built-in
  // check-digit function (luhn, npi, dea, iban, nhs) — config supplies data,
  // never code.
  "recognizers": [
    {
      "entity_type": "EMPLOYEE_ID",
      "regex": "\\bEMP-\\d{6}\\b",
      "base_score": 0.85,
      "context": ["employee", "badge"],
      "context_required": false
    }
  ],

  "audit_log": true
}

A ready-to-copy example lives at examples/umbryn_config.json.

Entity coverage

Entity

Regex engine (default)

Presidio engine ([presidio])

Email, Phone, SSN, Credit card, IP, URL

NPI (Luhn + 80840 check digit)

DEA (check digit)

Medicare MBI (position-typed)

MRN (context-anchored)

Medicare HICN (SSN + beneficiary code)

CLIA lab number

US ITIN (9XX-range structure)

UK NHS number (mod-11 check)

Canadian SIN (Luhn check)

US driver's license (context-anchored)

IBAN (mod-97 / ISO 7064 check)

Person names

✅ (spaCy NER)

Addresses / locations

✅ (spaCy NER)

Custom recognizers (your regex + check digit, via config)

Benchmark

Detection quality is measured, not asserted. The numbers below are the default (zero-dependency) engine scored against the synthetic eval corpus — 200 generated documents, ~1,800 labeled spans, with checksum-failing look-alikes woven in as distractors to keep precision honest. Reproduce them with python eval/run_eval.py --markdown.

Entity

Precision

Recall

F1

TP

FP

FN

CANADA_SIN

1.00

1.00

1.00

87

0

0

CLIA_NUMBER *

1.00

1.00

1.00

105

0

0

CREDIT_CARD

1.00

1.00

1.00

72

0

0

DEA_NUMBER *

1.00

1.00

1.00

119

0

0

EMAIL_ADDRESS

1.00

1.00

1.00

144

0

0

IBAN_CODE

1.00

1.00

1.00

87

0

0

IP_ADDRESS

1.00

1.00

1.00

62

0

0

MEDICAL_RECORD_NUMBER *

1.00

1.00

1.00

200

0

0

MEDICARE_BENEFICIARY_ID *

1.00

1.00

1.00

126

0

0

MEDICARE_HICN *

1.00

1.00

1.00

78

0

0

NPI *

0.94

1.00

0.97

200

12

0

PHONE_NUMBER

1.00

1.00

1.00

144

0

0

UK_NHS_NUMBER

1.00

1.00

1.00

95

0

0

US_DRIVERS_LICENSE *

1.00

1.00

1.00

81

0

0

US_ITIN

1.00

1.00

1.00

97

0

0

US_SSN *

1.00

1.00

1.00

136

0

0

\* = HIPAA-relevant identifier, subject to the CI quality gate. Aggregate over the gated set: precision 0.99, recall 1.00. The gate fails the build if recall drops below 0.90 or precision below 0.80. (NPI's 12 false positives are look-alike 10-digit numbers that happen to pass the Luhn/80840 check digit — a deliberate, fail-safe bias toward over-redaction.)

These are synthetic, best-case conditions with clean formatting and nearby context words; real-world text is messier. Treat this as a regression guardrail and a sanity check, not a guarantee — always evaluate on your own representative data.

Scope & honest limitations

This tool reduces PHI/PII exposure at one boundary. It does not make a system "HIPAA compliant." Compliance is a property of an entire system and organization — its policies, contracts, access controls, audit posture, and people — not of any single library. Running umbryn-mcp can be part of a compliant design, but it is not a certification, a guarantee, or a substitute for a Business Associate Agreement, a risk assessment, or legal counsel.

Concretely, this project does not: guarantee 100% detection (no detector does), de-identify beyond reversible redaction, cover non-text data, or act as a transparent proxy in v1 (redaction is via explicit tool calls you wire in). No detector is perfect — evaluate on your own representative data before relying on it. See docs/THREAT_MODEL.md for the full boundary, assumptions, and residual risks, and SECURITY.md to report issues.

How to contribute

Contributions are very welcome — this is a deliberately friendly place to make your first open-source PR, and the maintainer tries to respond quickly.

The easiest high-value contribution: add a detection recognizer for a new identifier (a regex + an optional check-digit validator + a test). The add-a-recognizer issue form doubles as the spec, and CONTRIBUTING.md walks through the six steps.

Other good ways to help: improve docs, add test cases or example client configs, or pick up something from the roadmap. Browse good first issues or open an issue to propose something.

git clone https://github.com/Rinava/umbryn-mcp && cd umbryn-mcp
pip install -e ".[dev]"
pytest                 # fast invariant suite (Presidio faked, sub-second)
ruff check . && mypy src/umbryn_mcp
python eval/run_eval.py

The full guide — dev setup, conventions, and the no-real-PHI rule for fixtures — is in CONTRIBUTING.md. By contributing you agree your work is MIT-licensed.

License

MIT — matches Presidio and maximizes reuse. Built with Microsoft Presidio (optional) and the MCP Python SDK.

umbryn-mcp is built by the team behind Kenda.

Available Tools

3 tools
detectDetect PHI/PIIA

Report the PHI/PII entities found in text without modifying it.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesText to inspect

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYesNumber of entities found
entitiesYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description bears full responsibility. It explicitly states 'without modifying it', which signals non-destructive behavior. However, it lacks details about output format, error handling, or what entities are considered PHI/PII, leaving some behavioral ambiguity.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence of 10 words that front-loads the key purpose. Every word earns its place, with no redundancy or unnecessary detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one parameter and an output schema (context signal), the description covers the core functionality and non-modifying behavior. It could mention expected output shape or limitations, but the presence of an output schema mitigates the need for extensive return value details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with a clear parameter description 'Text to inspect'. The description adds context about PHI/PII entities, but this aligns with the tool's purpose rather than enhancing parameter understanding. The schema already sufficiently explains the parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reports PHI/PII entities without modifying text. It distinguishes itself from sibling tools 'redact' and 'restore' by explicitly noting the non-modifying nature, making its purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not provide explicit guidance on when to use this tool versus the sibling tools 'redact' and 'restore'. While the context implies detection is a prerequisite for redaction, no direct usage instructions or alternatives are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

redactRedact PHI/PIIA

Replace PHI/PII in text with reversible typed placeholders.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesText to scrub

Output Schema

ParametersJSON Schema
NameRequiredDescription
entitiesYesWhat was redacted, for auditing
token_mapYesplaceholder -> original value; keep local, do NOT send to the model
redacted_textYesText with PHI/PII replaced by typed placeholders

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description bears full burden. It discloses that redaction is reversible and uses typed placeholders, but does not elaborate on side effects, permission requirements, or output format details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence that communicates the core functionality. It is front-loaded and free of superfluous content, though it could benefit from additional context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple tool (one required parameter, output schema exists), the description provides sufficient context about the redaction operation and its reversible nature. It complements the siblings without needing extra details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema covers 100% of parameters with a clear 'text' parameter description. The tool description adds minimal extra meaning beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly specifies the action ('Replace'), the resource ('PHI/PII in text'), and the nature of the output ('reversible typed placeholders'). It effectively distinguishes from sibling tools 'detect' and 'restore' by implying the redaction step.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus its siblings ('detect' for identification, 'restore' for reversal). It omits prerequisites, exclusions, or typical workflow context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

restoreRestore redacted textB

Reverse a redaction using its token map, recovering the original text.

ParametersJSON Schema
NameRequiredDescriptionDefault
token_mapYesplaceholder -> original value
redacted_textYesText containing placeholders

Output Schema

ParametersJSON Schema
NameRequiredDescription
textYesThe rehydrated original text

TDQS

B3.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must disclose behavioral traits. It only states the core function without mentioning side effects, required permissions, error cases, or behavior when token map is incomplete.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that is clear and efficient, but it could be more structured with separate sections for purpose and usage.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given full schema coverage and an output schema, the description is adequate for a simple tool, but it lacks usage guidelines and behavioral transparency, which are gaps for completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description reuses the schema descriptions for both parameters, adding no additional meaning beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's action ('Reverse'), the resource ('a redaction'), and the method ('using its token map, recovering the original text'). This distinguishes it from sibling tools 'detect' and 'redact'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when you have a redacted text and token map to restore, but it does not explicitly state when to use this tool versus alternatives, nor does it provide prerequisites or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observeddetect
    • First observedredact
    • First observedrestore

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a distinct and non-overlapping purpose: detect finds entities, redact replaces them, and restore reverses the redaction. No ambiguity.

Naming Consistency5/5

All tool names are single verbs (detect, redact, restore) following a consistent pattern, making them easily predictable.

Tool Count5/5

Three tools exactly cover the full PHI redaction workflow (detect, redact, restore) without unnecessary extras or missing steps.

Completeness5/5

The tool set provides a complete pipeline: detection, redaction with reversible placeholders, and restoration. No obvious gaps for the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessWithin a week

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP proxy that pseudo-anonymizes PII before data reaches external AI providers like Claude, ChatGPT, or Gemini.
    18
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    MCP server for automatic detection and redaction of PII in text, with anonymization and deanonymization capabilities, all local processing.
    1
    -
  • A
    license
    A
    quality
    B
    maintenance
    MCP server and CLI for detecting, redacting, and auditing PHI in medical text before it is sent to AI agents, with tools for scan, redact, audit, and validate operations.
    4
    MIT