Skip to main content
Glama
invoice-match-mcp

invoice-match-mcp

README.md
# invoice-match-mcp

A small [MCP](https://modelcontextprotocol.io) server that lets an AI model **three-way-match supplier invoices** (invoice vs purchase order vs goods received) without ever being the one who decides.

The model calls the tools and explains the outcome. The decision comes from a deterministic rule engine, so it is reproducible, testable and auditable. That split is the point of this repo: *LLM as interface, rules as policy, evals as proof.*

## What it exposes

| MCP item | Purpose |
|---|---|
| `check_invoice(invoice_json, register=false)` | Returns `APPROVE`, `HOLD_PRICE`, `HOLD_QTY`, `HOLD_NO_PO`, `HOLD_DUPLICATE`, `HOLD_CURRENCY` or `REVIEW`, with human-readable reasons. Never posts or pays anything. |
| `list_open_purchase_orders(supplier)` | Lets the model look up PO lines, received quantities and agreed prices before it answers. |
| resource `policy://matching` | The policy in plain language, so the model can quote it. |

Rules: unit price within 2 % of the PO; invoiced quantity not above received quantity; same currency; same supplier plus invoice number twice is a duplicate; no PO number gives `REVIEW` at best, never `APPROVE`.

## Run it

Requires Python 3.10+ and the MCP Python SDK 2.x.

```bash
pip install -e ".[dev]"
python -m invoice_match.server        # stdio MCP server
```

Claude Desktop / any MCP client:

```json
{ "mcpServers": { "invoice-match": { "command": "python", "args": ["-m", "invoice_match.server"] } } }
```

Use your own purchase orders by pointing `INVOICE_MATCH_PO_FILE` at a CSV with the columns of `data/purchase_orders.csv`.

## Evals

```bash
python evals/run_evals.py
```

`evals/golden_cases.json` holds 11 invoices with the expected decision (clean match, price drift, over-billing, unknown PO, wrong supplier, currency, ambiguous PO, ...). The same file can grade an LLM agent: give the model each invoice with the server attached and compare its final answer to `expect`. One source of truth for engine, tools and agent.

`pytest` covers the engine (including the exact 2 % boundary and purity) and starts the real server over stdio, calling the tool the way a model would.

## Why this exists

Built by [Paweł Boruta-Ośko](https://www.linkedin.com/in/pawel-boruta-osko/), who ran an omnichannel retailer for almost 20 years (ERP, WMS, 50+ integrations) and now builds agentic systems. See also [OpenFill](https://github.com/Pawel-BoOssko/OpenFill), an MCP browser agent. All data in this repo is fictional. Written with substantial help from Claude (Anthropic): the matching rules and scope were specified by the author, the code, tests and evals were drafted by Claude Code and reviewed by him.

MIT licence.

TDQS

B3.4/5.0

Scored across 2 tools

Disambiguation5/5

The two tools target clearly different actions on different resources: one reads PO lines for a supplier, the other performs a three-way match on a single invoice. There is no realistic way to confuse them, and the descriptions reinforce the boundary (read-only listing vs. decision-returning validation).

Naming Consistency4/5

Both names follow a snake_case verb_noun convention (list_..., check_...) that reads naturally and consistently. The only minor deviation is that list_open_purchase_orders stacks multiple qualifiers while check_invoice is terse, but the pattern is still predictable.

Tool Count3/5

Two tools is thin even for a narrowly scoped three-way match server; the whole workflow lives in a single check_invoice call with one supporting lookup. It is functional and focused, but borderline under-provisioned for a stated accounts-payable domain.

Completeness3/5

The core path (fetch PO lines with ordered/received quantities, then match an invoice) is covered, and the register flag gives basic duplicate handling. However there is no way to look up a previously registered invoice, list invoices for a supplier, inspect prior decisions, or re-check a HOLD after correction, leaving notable gaps in the lifecycle.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues