wardcat-mcp
# wardcat-mcp
An [MCP](https://modelcontextprotocol.io) server that exposes
[wardcat](https://github.com/oguzhantopcu0/wardcat)'s **on-prem PII detection and
anonymization** as tools any agent can call — Claude Desktop, Cursor, a
self-hosted bot, a RAG pipeline. Use it as a **guardrail**: sanitize inputs
before they reach an LLM, or gate them with a semantic "is this sensitive?"
check.
> **Runs locally, stays local.** The server runs on your machine over stdio;
> the text, the models, and all detection stay on-prem — nothing is sent
> anywhere. Publishing this package ships *code you run yourself*, not a hosted
> service.
## Tools
| Tool | Description |
|------|-------------|
| `scan(text, entities?)` | Detect PII and return the **sanitized text** plus a PII-free summary — entity types, actions, confidence. The summary never carries raw values; `sanitized_text` echoes the original only under the `warn` action. Pass `entities` to limit the call to a subset of the enabled types. |
| `redact(text, action, entities?)` | Like `scan`, but you choose the **action per call**: `redact` drops the value (`[EMAIL]`), `mask` keeps a hint (`b***@acme.com`, last-4 of a card), `hash` gives a stable salted pseudonym (`[EMAIL:3245e00b…]`), `warn` leaves the text untouched but still reports what was found. Defaults to `WARDCAT_ACTION`. |
| `is_sensitive(text)` | Holistic LLM yes/no on whether the text contains sensitive information. Requires the LLM layer (`WARDCAT_LLM_MODEL`). |
| `server_info()` | Report the enabled entity types, the default action, and whether the NER / LLM layers are active — so an agent can discover capabilities without trial and error. |
All tools return **structured output** (a typed schema, not a JSON string) and are annotated read-only.
> **Threat model — what this protects.** wardcat-mcp guards what leaves the
> *agent*: it sanitizes text before it is logged, stored, or forwarded to a
> downstream API. It does **not** hide anything from the host LLM that is
> orchestrating the tool call — by the time a model invokes `scan`, it has
> already read the raw text (and it may be retained in that provider's context
> or logs). To filter text *before* it reaches any LLM, call the
> [wardcat](https://github.com/oguzhantopcu0/wardcat) library in-process instead.
## Install & run
Not published to PyPI — run it straight from the repository (its wardcat
dependency does come from PyPI, so this needs no other source):
```bash
# Run directly from GitHub, no install:
uvx --from git+https://github.com/oguzhantopcu0/wardcat-mcp.git wardcat-mcp
# from a local clone:
uv run wardcat-mcp
# with the SpaCy NER layer (PERSON/ORG/ADDRESS), from a clone:
uv run --extra ner wardcat-mcp
```
### Docker
The server talks over stdio, so run the container interactively (`-i`):
```bash
docker build -t wardcat-mcp .
docker run -i --rm -e WARDCAT_SALT=your-secret wardcat-mcp
# with the SpaCy NER layer:
docker build --build-arg EXTRAS='[ner]' -t wardcat-mcp:ner .
```
In an MCP client, point `command` at `docker` with `args` `["run", "-i", "--rm",
"-e", "WARDCAT_SALT=your-secret", "wardcat-mcp"]`.
## Add it to an MCP client
Claude Desktop (`claude_desktop_config.json`), Cursor, Cline, Zed, etc.:
```json
{
"mcpServers": {
"wardcat": {
"command": "uvx",
"args": ["--from", "git+https://github.com/oguzhantopcu0/wardcat-mcp.git", "wardcat-mcp"],
"env": {
"WARDCAT_SALT": "your-secret-salt",
"WARDCAT_ACTION": "redact",
"WARDCAT_LLM_MODEL": "llama3.2:3b"
}
}
}
}
```
## Configuration (environment variables)
| Var | Default | Meaning |
|-----|---------|---------|
| `WARDCAT_SALT` | `""` | Hashing salt (required for the `hash` action). |
| `WARDCAT_ENTITIES` | broad structural + name set | Comma-separated entity types to enable. |
| `WARDCAT_ACTION` | `redact` | `warn` \| `hash` \| `redact` \| `mask`. |
| `WARDCAT_SPACY_MODEL` | — | Enable SpaCy NER with this model (needs the `ner` extra). |
| `WARDCAT_LLM_MODEL` | — | Enable the on-prem LLM layer via Ollama (e.g. `llama3.2:3b`). |
| `WARDCAT_LLM_BASE_URL` | `http://localhost:11434` | Ollama endpoint. |
## Development
```bash
uv sync --dev
uv run pytest # deterministic, regex-only — no models or network needed
uv run ruff check .
uv run mypy src
```
## Disclaimer
wardcat is a **best-effort** PII detector — it does not catch everything and is
**not legal advice or a substitute for compliance review** (e.g. GDPR/KVKK).
Validate it against your own data. Provided "as is" (MIT).
## License
MIT — see [LICENSE](LICENSE).
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: is_sensitive is a boolean guard, redact applies a specific action, scan returns sanitized text, and server_info reports configuration. No overlap in functionality.
All tool names are in snake_case and descriptive, but the pattern varies: is_sensitive uses 'is_', redact and scan are single verbs, and server_info is a noun pair. While readable and consistent in style, it's not a uniform verb_noun pattern.
Four tools is well-scoped for a PII detection and redaction server. Each tool serves a core function without unnecessary bloat or gaps, fitting the server's purpose perfectly.
The tool set covers essential operations: boolean check (is_sensitive), configurable redaction (redact), full scan with summary (scan), and configuration discovery (server_info). Minor gaps like batch processing could exist, but the core workflow is complete.