pii-mcp
Pattern-based PII scrubbing for MCP servers (regex + checksums), with an
optional NER pass for person names in the Rust backend. Masks emails, IBANs,
cards, BICs, MACs, IMEIs, IPs, coordinates, BSNs, US SSNs, German tax IDs,
Dutch BTW-ids, Dutch passport/ID numbers, phones, street + house number
addresses, Dutch postcodes, and Dutch license plates in tool results.
Language packs: en, nl, and opt-in de.
Example
from pii_mcp import scrub_text
scrub_text(
"Contact ada@example.com. IBAN NL91 ABNA 0417 1643 00. card 4111111111111111"
)
# Contact [EMAIL]. IBAN [IBAN]. card [CREDIT_CARD]
Install
pip install "pii-mcp[fastmcp]" # FastMCP >= 3.0.0
# or
pip install pii-mcp # core; platform wheels include Rust acceleration
# or
npm install pii-mcp
FastMCP
from fastmcp import FastMCP
from pii_mcp.fastmcp import PiiScrubMiddleware
mcp = FastMCP("MyServer")
mcp.add_middleware(PiiScrubMiddleware()) # languages=["en", "nl"] by default
# mcp.add_middleware(PiiScrubMiddleware(languages=["en", "nl", "de"]))
Results only. On scrub failure or oversize, the result is withheld, never forwarded unmasked.
Core
from pii_mcp import scrub_text, scrub_payload
scrub_text("mail ada@example.com")
scrub_payload({"email": "ada@example.com"}, languages=["en"])
Scope
Aligned with AP: wat zijn persoonsgegevens where pattern/checksum detection can reach them. Person names need the opt-in NER build (below). Free-text health data (allergies), photos/audio/video, and unstructured klant-/personeelsnummers stay out of scope.
AP coverage (pattern layer)
| AP example / category | Detector | Notes |
|---|---|---|
| e-mail / contact | email, phone |
Incl. Unicode (EAI/IDN) email |
| IP-adres | ip |
Indirect identifier |
| Locatiegegevens | location |
Decimal and DMS lat/lon |
| Financiële gegevens | iban, credit_card, bic, vat_id |
|
| BSN / nationaal ID | bsn, passport |
Passport/NIK format (nl pack) |
| Online / device IDs | mac, imei |
IMEI: grouped forms + Luhn |
| Adres | address |
Street + number; NL postcode |
| Kenteken | license_plate |
nl pack |
| Naam | person |
Opt-in NER build only |
| Pasfoto, allergieën, koopgedrag, camera | Media / free text |
Person names (optional NER)
ner=True runs an XLM-R token classifier
(Davlan/xlm-roberta-base-ner-hrl,
AFL-3.0; EN/NL/DE among its languages) after the pattern detectors and masks
person names as [PERSON]. It runs in the Rust core on
candle (CPU, fp32) and is off by
default: published wheels and the npm package are built without it, and no
weights ship with any package.
Build with the ner feature:
maturin develop --release --features ner # Python
(cd typescript && npm run build:native -- --features ner) # Node
cargo build -p pii-core --features ner # Rust
Download the pinned weights (1.1 GB) and the matching XLM-R tokenizer (MIT)
into one directory, and point PII_MCP_NER_MODEL at it:
mkdir -p ner-model && cd ner-model
M=https://huggingface.co/Davlan/xlm-roberta-base-ner-hrl/resolve/253f557bd8249b8515114cfd7f71974fe5fa4d2f
T=https://huggingface.co/FacebookAI/xlm-roberta-base/resolve/e73636d4f797dec63c3081bb6ed5c7b0bb3f2089
curl -fL -O "$M/config.json" -O "$M/model.safetensors" -O "$T/tokenizer.json"
export PII_MCP_NER_MODEL="$PWD"
scrub_text("Mail Ada Lovelace at ada@example.com", ner=True)
# {'text': 'Mail [PERSON] at [EMAIL]', ...}
- Pattern hits are masked first; the NER pass never re-tags a placeholder, so nothing is counted twice.
PII_MCP_NER_THRESHOLD(default0.9) is the minimum person probability per token. The default favors precision; lower it (for example0.5) to mask more names at the cost of more false positives.ner=TrueraisesPiiScrubErrorwhen the build has nonerfeature, the backend is pure Python / TypeScript, or the model directory is missing or invalid. Text is never returned with the NER pass silently skipped.
Cost on macOS arm64 (M4 Pro), scripts/bench_backends.py with
PII_MCP_NER_MODEL set:
| Case | Regex only | ner=True |
|---|---|---|
| Short mixed PII | 0.028 ms | 67 ms |
| 2 KiB tool result | 0.22 ms | 532 ms |
| 16 KiB tool result | 2.3 ms | 4.6 s |
Inference time grows with input length, at about 0.3 s per KiB here: a 1 MiB
tool result takes about 5 minutes. Use ner=True for results of a few KiB,
not for bulk text. Model load takes 2.0 s once per process; peak RSS is about
2.4 GB. Inference is fp32 only: candle's XLM-R implementation builds its
attention mask in F32, so fp16 weights do not run.
On the eval (PII_MCP_NER_MODEL=<dir> python eval/score.py --ner, EN/NL/DE
names in 15 contexts), person scores recall 0.987 and precision 0.931. Most
false positives are company names such as Albert Heijn. The address
generators only produce the street shapes the patterns support, so the
address recall of 1.0 covers those shapes only; precision is measured with
the en, nl and de packs against bare street names and company names.
Words that end in a street suffix and are followed by a number (Keypad 3,
Supermarkt 24) are masked on purpose, since dropping those suffixes leaks
real addresses such as Nieuwmarkt 4.
Packages
| Runtime | Path | Install |
|---|---|---|
| Python | src/pii_mcp |
pip install pii-mcp |
| TypeScript | typescript/ |
npm install pii-mcp |
| Rust core | crates/pii-core |
shared by both (PyO3 / N-API) |
Rust core (Python)
Published platform wheels ship pii_mcp._native (PyO3 over pii-core). Pip
prefers those on supported OS/arch; elsewhere (or with --no-binary) you get
the pure-Python hatchling wheel/sdist and the same scrub API.
When _native is importable, scrub_text / scrub_payload use it. Force the
Python path with PII_MCP_BACKEND=python; require native with
PII_MCP_BACKEND=native.
Local development from a clone (optional):
pip install -e ".[native]"
maturin develop --release --manifest-path crates/pii-mcp-native/Cargo.toml
Performance (Python vs Rust release)
Medians from scripts/bench_backends.py on macOS arm64 / CPython 3.14.7
(release native build; debug builds are not representative):
| Case | Python | Rust | Speedup |
|---|---|---|---|
| Short clean text | 0.234 ms | 0.023 ms | 10.2x |
| Short mixed PII | 0.058 ms | 0.007 ms | 8.9x |
| 100 KiB sparse PII | 20.4 ms | 2.0 ms | 10.2x |
| 1 MiB sparse PII | 203 ms | 20.4 ms | 10.0x |
| Nested JSON payload | 12.4 ms | 1.2 ms | 10.3x |
1k x tiny scrub_text |
54.3 ms | 6.8 ms | 8.0x |
python scripts/bench_backends.py
Metadata
Release files for pii-mcp 1.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pii_mcp-1.7.0.tar.gz | 235.8 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| pii_mcp-1.7.0-py3-none-any.whl | Python 3 | none | any | Details |
| pii_mcp-1.7.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| pii_mcp-1.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| pii_mcp-1.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| pii_mcp-1.7.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| pii_mcp-1.7.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 4.6 MB
Release files / pii_mcp-1.7.0.tar.gz
| Download URL | pii_mcp-1.7.0.tar.gz |
|---|---|
| Size | 235.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
68455e13950ca4c062e545307fdbfad37f7933274f7cf9f066f94f8b505387f2
|
|
BLAKE2b-256 checksum How to use checksums |
84153d46f53c6a79a1b83c3e0a064b577282ca2b1f9aed414b79542ae5338129
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / pii_mcp-1.7.0-py3-none-any.whl
| Download URL | pii_mcp-1.7.0-py3-none-any.whl |
|---|---|
| Size | 28.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
31a57ea010a7392cabfac055cbcbf05b72279195a07ddb787d38fdc7a32c3428
|
|
BLAKE2b-256 checksum How to use checksums |
737f5c17ca047f21b015b4c411715a4e83c834c853e081585119d70a68eccc7a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / pii_mcp-1.7.0-cp310-abi3-win_amd64.whl
| Download URL | pii_mcp-1.7.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 799.9 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
65e3a7381a813333b56f009307ee6d5ab6fda55ea97ae01fe2b4b1c24a56795b
|
|
BLAKE2b-256 checksum How to use checksums |
a792343d3f36725a08470f9f413b4556863c4926b4e44162683cbcc460e059ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / pii_mcp-1.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pii_mcp-1.7.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 952.9 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
73e2e877d04ad5a74c7d92bfb76f599cb7311b06678c96dc33736bfb1b8d3508
|
|
BLAKE2b-256 checksum How to use checksums |
fbe4cda6d27abe0b7814bcbd10e2f49f355fae1cd66780eea78e5593a85d33f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / pii_mcp-1.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pii_mcp-1.7.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 922.6 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
c7d50741b0087ab008df2a8ece910c17db41ca42412482e9814584f37a1d61b6
|
|
BLAKE2b-256 checksum How to use checksums |
8dd60c8b4ac945092c695c7ec0926c237d9667810255433c718e57a5d3fb891d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / pii_mcp-1.7.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | pii_mcp-1.7.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 831.0 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
14c2745fffbf5dd808b6ef105f96e8fc6241810ccc46dc0a39a9cfe3e94c6543
|
|
BLAKE2b-256 checksum How to use checksums |
5d2bf3c17d6e567ca3f26eeb7321b64b41dbff0072861063beb9bb2f3a47f156
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / pii_mcp-1.7.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | pii_mcp-1.7.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 878.8 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
9da3f632e43ae40416cf84dfb6ab131db396e1e8139d1fc646485425144cbe72
|
|
BLAKE2b-256 checksum How to use checksums |
4334065eb8a3f60e9d215668c09786bb1fd185669f634a5a6354dafc9790f3e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency log