Regulated AI · MCP · Masking demonstration

Document Processing
Pipeline

I built an experiment that extracts document details, masks selected personal information, checks the results, and sends exceptions for human review. It is not a compliance certification.

5 MCP tools PII masking + schema validation Human-review queue Python · Node
At a glance 30-second read

What it is

I built five document-processing tools for extraction, masking personal information, checking required fields and rules, human review, and cost reporting. The tools work with Claude. This personal experiment does not establish compliance.

What I explored

I explored how to separate automated checks from decisions that need a person.

Skills and tools

  • MCP tool design — one stage per tool, no shared state
  • Two-layer PII masking (path-based + regex + free-text flagging)
  • JSON Schema + business-rule validation (ajv)
  • Human-in-the-loop queue with auto-prioritization
  • Token-cost telemetry and per-doc cost forecasting
  • Work-order pattern — model-agnostic at the seam
MCP SDK Python Node JSON Schema ajv PII Masking Masking demonstration
01 — The pipeline 5 stages · 3 patterns · ~1240 lines of source code

Five steps, with human review.

I separated the process into tools that extract, mask, check, route, and total costs. Claude chooses the tools. Exceptions go to a person for review.

stage 1 doc_extract mutation · work-order Returns the task, schema, and 13 rules. Claude does the extraction against the document text.
stage 2 pii_mask mutation · two-layer Two-layer hide: known fields by path, plus regex matches anywhere. Free-text fields get sent back to Claude for a second look.
stage 3 doc_validate critique · redaction-aware Schema check (with ajv) + 12 business rules. Skips format checks on fields where the data is hidden.
stage 4 hitl_route mutation · triage Sends exceptions to a queue. Auto-priorities based on the rule type. Each entry comes with a suggested fix.
stage 5 cost_report aggregation Per-stage tokens × rates. Totals, plus what this would cost per 1,000 and per 100,000 documents.
Design rationale Splitting the work by stage splits it by concern. Each tool's input is the previous tool's output — no shared state. I can review each step’s output, replace one tool, and compare prompts side by side. The work-order pattern keeps the demo runnable on a Claude Max subscription; production with direct API access plugs in at doc_extract without touching the four downstream tools.
02 — A real run 4 pre-baked traces · click a tab

Pick a doc. Watch it move.

Four runs: three clean documents, plus one claim with deliberately broken amounts and a missing required field. The traces below are real outputs from the pipeline runner — same JSON the MCP tools produce when run end-to-end.

03 — Inside the work-order pattern no API key required · production-portable

Tools return contracts, not responses.

doc_extract doesn't call Claude — it's a tool that returns a structured work order for Claude to carry out. Production with direct API access plugs in here without touching any downstream stage.

tools/call · doc_extract [ MUTATION · WORK ORDER ]
request
{
  "doc_text": "NORTHSTAR MUTUAL — AUTO CLAIM SUBMISSION\nClaim ID: NSM-2026-0451829\n...",
  "doc_type": "claim"
}

response
{
  "task": "Extract structured data from this claim document. Conform exactly to the schema. Return JSON only.",
  "doc_type": "claim",
  "schema": { /* full JSON Schema for AutoClaim — types, requireds, patterns, formats */ },
  "extraction_rules": [
    "Output JSON only, conforming exactly to the provided schema. No commentary, no markdown fences.",
    "Use ISO 8601 for all dates (YYYY-MM-DD) and date-times (YYYY-MM-DDTHH:mm:ssZ or with offset).",
    "Currency values: strip $ and commas, return as numbers.",
    "Missing fields → null. Do not invent values.",
    "policyholder.address.state should be the two-letter USPS code.",
    "vehicle.year is an integer, not a string.",
    /* 7 more rules */
  ],
  "output_format": "Plain JSON, valid against the schema. No prose, no fences, no comments.",
  "on_completion": "Pass the extracted JSON to pii_mask with doc_type='claim'.",
  "estimate": { "stage": "extract", "input_tokens": 1955 }
}
Why the tool doesn't call Claude In MCP, Claude is the agent calling the tools. Asking the server to call Claude AGAIN to do the extraction is double work for no gain — and locks every demo to an API key. Returning a work order keeps Claude as the executor. The server's job is to encode the contract: which schema, which rules, what to do with the result. Claude reads the contract, generates the JSON, hands off to the next stage. Production swaps the work order for an Anthropic API call at this single seam; the downstream tools don't notice.