{
  "markdown": "# invoice-intake-mcp\n\n<!-- mcp-name: com.justdukkan/invoice-intake-mcp -->\n\nReference implementation of the tool-design rules in\n[Designing MCP servers and tools that agents can use safely](https://justdukkan.com/insights/mcp-server-and-tool-design/),\non a real back-office process: supplier invoice intake.\n\nIt is small on purpose. The fake ERP is a JSON file; everything else is the\nshape we use in production systems.\n\n## What it shows\n\n| Rule | Where |\n|---|---|\n| Model the business operation, not the API | `erp_match_po`, `erp_stage_invoice`, not `PATCH /invoices` |\n| Separate reads, drafts and commits | `--role read` server (reads + reversible staging) vs `--role commit` server (irreversible post) |\n| Strict schemas | `Literal` enums for currency, ids not names, `found=false` as a valid empty result |\n| Permissions on the server and credential | specialist agents get *read*; only the orchestrator connects to *commit* |\n| Tool results are untrusted input | `invoices_extract` says so in its description; the eval set includes a prompt-injection case |\n| Typed errors | `{\"error\": \"approval_required\" \\| \"validation_failed\" \\| \"business_rule\" \\| \"not_found\", \"retryable\": bool, ...}` |\n| Log for the auditor | `audit.jsonl`: actor, tool, ids, approval reference |\n| Human checkpoint enforced server-side | `erp_post_invoice` refuses without a recorded approval, whatever the client believes |\n\n## Run it\n\n```bash\npython -m venv .venv && . .venv/bin/activate\npip install -e \".[dev]\"\n\npython -m invoice_intake_mcp.orchestrator            # process the inbox\npython -m invoice_intake_mcp.approve list            # see what is waiting\npython -m invoice_intake_mcp.approve APR-xxxx approved --by cfo\npython -m invoice_intake_mcp.orchestrator --resume   # post the approved item\ncat audit.jsonl\n```\n\nExpected first run:\n\n```\nplan: 4 new invoices -> extract, resolve vendor, match, stage, post-or-approve\n  invoice_2291.pdf: staged STG-… -> WAITING for APR-…        (7,420 EUR > 5,000 limit)\n  invoice_2292.pdf: match=goods_not_received -> REVIEW\n  invoice_2293.pdf: match=variance {\"variance_pct\": 2.34} -> REVIEW\n  invoice_2294.pdf: no PO reference -> REVIEW (ask requester)\n```\n\nThe orchestrator is deterministic so the flow replays without an API key.\nAn LLM belongs in the places marked in `orchestrator.py` (ambiguous vendor\ncandidates, free-text remarks, the note back to the requester), not in the\nmatch rule, the tolerance or the approval limit.\n\n## Use the servers from an MCP client\n\n```json\n{\n  \"mcpServers\": {\n    \"invoice-intake-read\":   { \"command\": \"uvx\", \"args\": [\"invoice-intake-mcp\", \"--role\", \"read\"] },\n    \"invoice-intake-commit\": { \"command\": \"uvx\", \"args\": [\"invoice-intake-mcp\", \"--role\", \"commit\"] }\n  }\n}\n```\n\nGive an agent only the *read* server unless it is the orchestrator.\n\n## Tests and evals\n\n```bash\npytest                 # unit tests on the core operations and the gate\npython evals/run.py    # replayable decision cases, run on every change\n```\n\n## Layout\n\n```\ninvoice_intake_mcp/\n  core.py          business operations + policy (tolerance, approval limit), pure functions\n  server.py        the two MCP servers and their tool descriptions\n  orchestrator.py  minimal hub over MCP stdio: read server for work, commit server for posting\n  approve.py       the human decision, as a CLI\n  audit.py         append-only JSONL audit log\n  erp.py           fake ERP / inbox state (JSON file)\ntests/             pytest\nevals/             cases.jsonl + run.py\n```\n\nMIT. Built by [JustDukkan](https://justdukkan.com), AI solutions architecture.\n",
  "bytes": 3578,
  "sha": "ba2c630eb8a2330df97dc3e133bc3a2655e3c8328fc569cf5c18b6931cd1ccac",
  "repo_slug": "justdukkan/invoice-intake-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_justdukkan_invoice_intake_mcp_458e4d50/readme"
}