Regulated AI · MCP · HIPAA-Grade

Document Processing
Pipeline

Five MCP tools, one per stage. Extract, PII mask, validate, route to human review, report cost. Model-agnostic — runs on Claude, GPT, Gemini, or any tool-calling model.

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

What it is

A document-processing MCP server for regulated environments. Five composable tools, one per stage, with deterministic PII masking, schema + business-rule validation, an HITL queue for exceptions, and per-document token-cost reporting. Built on Claude; portable to any tool-calling model.

Maps to

AI / Agent Engineer · Applied AI Engineer · AI Platform Engineer · Forward-Deployed Engineer (regulated)

Skills demonstrated

  • 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 HIPAA-Grade
01 — The pipeline 5 stages · 3 patterns · ~1240 lines of source code

Five tools, one conductor.

Each stage is a separate custom Claude tool, swappable on its own. The conductor is Claude. Three of the five tools change something (extract, mask, route); one is a check (validate); one totals things up (cost).

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. The pipeline stays inspectable: drop a tool's trace into Claude, swap one piece for another, test different 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.