omitly-mcp
v0.1.0
Published
Model Context Protocol server exposing Omitly's local, verifiable PDF redaction to AI agents. Redaction runs on-device — documents are never uploaded.
Maintainers
Readme
omitly-mcp
A Model Context Protocol server that exposes Omitly's local, verifiable PDF redaction to AI agents (Claude Code, Claude Desktop, and any other MCP client).
The point of difference: an agent can redact a document without uploading it anywhere. Redaction runs on-device through the Omitly engine and returns a signed audit log proving the data was removed — the opposite of pasting a confidential file into a chat model.
Four of the seven tools (find_sensitive_regions, locate_text,
check_redaction, verify_redaction) work out of the box — npm install,
no Rust toolchain, no native binary, no desktop app. They run on a
wasm-bindgen build of the same detector that powers the web leak-checker at
omitly.app, bundled directly in this package. create_pdf and the two
write tools (redact_pdf, redact_by_entity) still need a configured
native engine — see "Build & run" below.
Tools
| Tool | What it does |
|------|--------------|
| find_sensitive_regions | Scans a PDF on-device and returns PII candidates — email/SSN/phone/card plus Australian identifiers (TFN, ABN, ACN, Medicare, Centrelink CRN, IHI, BSB; check-digit validated where a published algorithm exists) — with page + exact coordinates, so the agent selects by entity and never guesses geometry. Best-effort pattern matching, not a compliance assessment. Optional regions (generic/us/au) narrows the listed kinds. |
| locate_text | Resolves literal strings the model supplies (names, addresses — anything regex can't catch) to their page + coordinates. The model does the recognition; the engine does the geometry. |
| redact_by_entity | One-shot: find + filter by kind (email/ssn/phone/card/tfn/abn/acn/medicare/crn/ihi/bsb) and/or regions + redact + verify. The "just scrub the obvious PII" shortcut. |
| redact_pdf | Removes the underlying data from given regions of a PDF, verifies nothing survives, writes the redacted file, and returns the audit log. |
| verify_redaction | Re-scans an already-redacted PDF and returns the verification verdict. |
| create_pdf | Generates a clean PDF from Markdown/HTML on-device, rendered through a real browser engine so it looks printed — instead of writing a throwaway reportlab/LaTeX script. |
PDF generation (create_pdf)
create_pdf is served by a separate binary, omitly-pdf (in
crates/omitly-pdf), kept apart from the redaction engine because generation is
a different trust model from verifiable redaction. It renders Markdown (or raw
HTML) through a headless Chromium-family browser (Chrome/Chromium/Edge/Brave;
override with OMITLY_BROWSER_BIN) — the same engine family the Omitly app's
webview uses, so output looks printed rather than script-generated. Build it and
point OMITLY_PDF_BIN at the binary:
cargo build -p omitly-pdf --release # → target/release/omitly-pdf// stdin
{ "command": "create", "outputPath": "/abs/out.pdf",
"source": "# Hello\n\nBody **markdown**", "format": "markdown", "title": "Hello" }
// stdout
{ "ok": true, "output": "/abs/out.pdf" }Typical agent flows:
- Quick:
redact_by_entity(find + redact + verify in one call). - Careful:
find_sensitive_regions/locate_text→ review →redact_pdf→verify_redaction. Coordinates fromfind/locatedrop straight intoredactas itsregionsargument.
See DEMO.md for a full Claude Code walkthrough.
Status
The MCP surface (seven tools, schemas, transport), the native engine binary
(crates/omitly-cli, built as omitly-redact), and the bundled wasm engine
(crates/leakcheck-wasm, covering the four free tools without a native
binary) are all implemented and pass end-to-end tests. find_sensitive_regions
is a first-pass detector (ASCII patterns, per-show-operator matching): treat
its hits as candidates for review, not a completeness guarantee. An LLM can
always supply additional regions directly.
Privacy of findings. Detection results are returned with a masked
preview (e.g. •••-••-6789), never the raw value. The file isn't uploaded and
the secret detected inside it isn't sent back through the model — redaction is
driven entirely by page + coordinates, so the plaintext stays on the machine.
Engine contract (implemented in crates/omitly-cli)
The server spawns OMITLY_REDACT_BIN, writes a JSON request to stdin, and reads
a JSON response from stdout. Any failure returns { "ok": false, "error": "..." }
(the process still exits 0, so the caller reads ok rather than the exit code).
// stdin
{ "command": "find", "pdfPath": "..." }
// stdout
{ "ok": true, "count": 2, "regions": [
{ "page": 0, "x": 250.4, "y": 610.4, "width": 79.2, "height": 14.4, "kind": "ssn", "preview": "•••-••-6789" } ] }
// `preview` is masked — the raw value never leaves the process; redaction is driven by coordinates.// stdin
{ "command": "redact", "pdfPath": "...", "outputPath": "...",
"regions": [{ "page": 0, "x": 72, "y": 700, "width": 200, "height": 14, "reason": "PII.SSN" }] }
// stdout — also writes "<outputPath>.audit.json" beside the file
{ "ok": true, "output": "...", "audit": { "verdict": "pass", "regions": [ ... ], "warnings": [], "metadataScrubbed": true } }// stdin — recovers the redacted regions from "<pdfPath>.audit.json"
{ "command": "verify", "pdfPath": "..." }
// stdout
{ "ok": true, "verdict": "pass", "regions": [ ... ], "metadataScrubbed": true }Build & run
Free tools only (find_sensitive_regions, locate_text, check_redaction, verify_redaction) — no native engine needed:
cd omitly-mcp
npm install # published releases ship the wasm build already bundled
npm run build # local/dev only: also runs wasm-pack (needs Rust + wasm-pack)
node dist/index.jsnpm install omitly-mcp from the registry gets a package with wasm/
already built — a published install never needs Rust. Building from
source (this repo) does need wasm-pack (cargo install wasm-pack) and
the wasm32-unknown-unknown target, since npm run build regenerates the
wasm bundle from ../crates/leakcheck-wasm via npm run build:wasm.
Everything, including create_pdf and the two write tools
(redact_pdf, redact_by_entity):
# 1. Build the local engine binaries (from the repo root)
cargo build -p omitly-cli -p omitly-pdf --release # → target/release/{omitly-redact,omitly-pdf}
# 2. Build and start the MCP server
cd omitly-mcp
npm install
npm run build
OMITLY_ENGINE_DIR=/abs/path/to/target/release node dist/index.jsOne env var covers both binaries: OMITLY_ENGINE_DIR is the directory holding
omitly-redact and omitly-pdf. Per-binary overrides (OMITLY_REDACT_BIN,
OMITLY_PDF_BIN) win over the directory when set. When OMITLY_ENGINE_DIR
(or OMITLY_REDACT_BIN) isn't set, find_sensitive_regions, locate_text,
and check_redaction transparently use the bundled wasm engine instead —
same detector, no native binary. verify_redaction does too, but with a
narrower check: without a native engine there's no <path>.audit.json
sidecar to verify specific regions against, so it falls back to a general
re-scan of the whole file (still useful — a non-empty result still means the
file isn't clean — just not the same rigor as the sidecar-based check).
find/redact need qpdf for the redaction pipeline (QPDF_BIN overrides the
PATH lookup). find alone (native or wasm) is read-only and works without it.
Access control
Every path in a tool call comes from the model, so the server confines all reads and writes to one allowed directory:
OMITLY_ALLOWED_DIR— set it in the MCP config (recommended). Without it, the directory the server was started in is used.- Symlinks are resolved before the check, so a link inside the root pointing outside it is refused.
- Outputs never overwrite an existing file (or its
.audit.jsonsidecar); the agent is asked to pick a fresh name instead. OMITLY_ENGINE_TIMEOUT_MS(default 120000) — a wedged engine process is killed at the deadline instead of hanging the agent's tool call.
These are guardrails against confused-deputy mistakes, not a sandbox against a
hostile local user — see docs/THREAT-MODEL.md.
Licensing
redact_pdf/redact_by_entity are the write surface, enforced inside the
engine binary (not in this server, and not bypassable by the bundled wasm
fallback — wasm never touches these two tools): a Pro or Personal licence
(OMITLY_LICENSE_FILE, or the Omitly desktop app's activated licence on the
same machine) runs unmarked; otherwise the shared 14-day trial applies and
the audit output is permanently marked as evaluation output. The redaction
itself is never degraded, and licence checks never touch the network.
find_sensitive_regions, locate_text, check_redaction, and
verify_redaction are free forever — the wasm build backing them has no
licence logic at all, because there's nothing to gate.
Register with Claude Code
claude mcp add omitly -- env \
OMITLY_ENGINE_DIR=/path/to/engine-dir \
OMITLY_ALLOWED_DIR=/path/agents/may/touch \
node /abs/path/to/omitly-mcp/dist/index.jsOr in Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"omitly": {
"command": "node",
"args": ["/abs/path/to/omitly-mcp/dist/index.js"],
"env": {
"OMITLY_ENGINE_DIR": "/path/to/engine-dir",
"OMITLY_ALLOWED_DIR": "/path/agents/may/touch"
}
}
}
}