Metadata-Version: 2.4
Name: reconcile-mcp
Version: 0.0.1
Summary: Model Context Protocol (MCP) server that reconciles ISO 20022 camt.053 bank-statement entries against expected pain.001 payments -- explainable exact, partial, one-to-many and many-to-one matching.
License: Apache-2.0
License-File: LICENSE
Keywords: iso-20022,reconciliation,camt053,pain001,mcp,payments,treasury,fintech
Author: Sebastien Rousseau
Author-email: sebastian.rousseau@gmail.com
Requires-Python: >=3.10,<4.0
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: mcp (>=1.2)
Project-URL: Homepage, https://reconcile-mcp.com
Project-URL: Repository, https://github.com/sebastienrousseau/reconcile-mcp
Description-Content-Type: text/markdown

# reconcile-mcp: An MCP Server for ISO 20022 Cash Reconciliation

**A [Model Context Protocol][mcp] server that matches *expected* payments
(from `pain.001` credit transfers) against *observed* booked entries (from a
`camt.053` statement) and returns an explainable reconciliation** — exact
matches, short/over payments, split settlements (one-to-many), batch credits
(many-to-one), and the residual unmatched items on each side, every match
carrying a score and the reasons it was made.

> **Latest release: v0.0.1** — 7 MCP tools over stdio, pure-Python matching
> engine, deterministic sandbox test-mode, for Python 3.10+. Part of the
> [ISO 20022 MCP suite](#the-iso-20022-mcp-suite): you own both sides of the
> match.

## Why this exists

Reconciliation is the treasury team's daily pain: did the money we *expected*
actually *arrive*, and which invoice does each credit belong to? It is rarely
one-to-one — customers underpay, settle an invoice in instalments, or a payout
aggregator sends one lump covering a dozen receivables. `reconcile-mcp` does
this matching as an agent tool, and — critically for finance — **shows its
work**: every pairing comes with a numeric score and a plain list of the
signals (reference, amount, date, counterparty) that drove it.

## Install

```sh
pip install reconcile-mcp
# or run without installing:
uvx reconcile-mcp
```

MCP client config (e.g. Claude Desktop `claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "reconcile": {
      "command": "reconcile-mcp"
    }
  }
}
```

## Quick start (zero real data)

The server ships a **sandbox test-mode**: deterministic scenarios so you can
run the whole flow with no setup and no real cash data. One call gets you a
full, explainable result:

```
run_sandbox_scenario(name="month_end")
```

returns a realistic mixed close — one clean match, one short payment, one split
settlement, and an unexpected credit correctly left unmatched:

```jsonc
{
  "summary": {
    "expected_count": 3, "observed_count": 5,
    "matched_expected": 3, "unmatched_observed": 1,
    "matches_by_type": {"exact": 1, "amount_mismatch": 1, "one_to_many": 1},
    "fully_reconciled": false
  },
  "matches": [
    {"type": "amount_mismatch", "expected": ["INV-6002"], "observed": ["ENT-52"],
     "amount_delta": "-99.99", "confidence": "high",
     "reasons": ["reference exact", "amount close (delta -99.99)", "date +/-0d", "counterparty exact"]},
    {"type": "exact", "expected": ["INV-6001"], "observed": ["ENT-51"], "amount_delta": "0.00"},
    {"type": "one_to_many", "expected": ["INV-6003"], "observed": ["ENT-53", "ENT-54"],
     "reasons": ["amount sum of 2 entries"]}
  ],
  "unmatched_observed": ["ENT-55"]
}
```

List every scenario with `list_sandbox_scenarios`; load one to inspect or edit
its inputs with `load_sandbox_scenario`.

## Bring your own data

Records are small canonical objects — `id` and `amount` required, everything
else optional and used to sharpen matching:

```jsonc
{
  "id": "INV-1001",            // your reference / end-to-end id
  "amount": 1200.00,
  "currency": "EUR",           // ISO 4217
  "date": "2026-03-02",        // ISO-8601
  "counterparty": "Acme Ltd",
  "reference": "INV-1001"      // remittance / structured reference
}
```

Already using the rest of the suite? Feed parsed output straight in — the
adapters map it for you:

- `normalize_pain001(document)` → the *expected* side, from
  [`pain001-mcp`][pain001-mcp].
- `normalize_camt053(document)` → the *observed* side, from
  [`camt053-mcp`][camt053-mcp].

Then call `reconcile(expected, observed)`.

## Tools

| Tool | What it does |
| --- | --- |
| `reconcile` | Match expected payments against observed entries; full explainable report. |
| `explain_match` | Score a single expected/observed pair with a per-signal breakdown (tuning aid). |
| `normalize_pain001` | Adapt parsed `pain.001` output into canonical *expected* records. |
| `normalize_camt053` | Adapt parsed `camt.053` output into canonical *observed* records. |
| `list_sandbox_scenarios` | List the built-in test-mode scenarios and magic references. |
| `load_sandbox_scenario` | Return one scenario's expected/observed inputs to inspect or edit. |
| `run_sandbox_scenario` | Load a scenario and reconcile it in one call — the fastest first run. |

## How matching works

Each candidate pair is scored on four weighted signals, then classified:

- **Reference** (0.45) — exact / partial equality of references and end-to-end
  ids, normalised to bare alphanumerics.
- **Amount** (0.35) — exact within tolerance, or a linearly-decaying closeness
  with the delta reported.
- **Date** (0.10) — proximity within a configurable window; neutral if unknown.
- **Counterparty** (0.10) — token-set overlap of names; neutral if unknown.

Assignment is greedy, highest-score-first and fully deterministic (a total
tiebreak order), so the same inputs always produce the same result. Residuals
are then tested for **one-to-many** (a bounded subset-sum: one expected settled
by several entries) and **many-to-one** (one entry covering several expected).

Tune any of it via the `options` argument: `abs_tol` / `rel_tol`,
`date_window_days`, `high_threshold`, `review_threshold`, `currency_strict`,
`enable_one_to_many`, `max_combination`.

## The ISO 20022 MCP suite

`reconcile-mcp` is the reconciliation layer of a family of vendor-neutral,
Python-native ISO 20022 MCP servers:

- [`pain001-mcp`][pain001-mcp] — customer credit transfer initiation (pain.001).
- [`pacs008-mcp`][pacs008-mcp] — FI-to-FI credit transfers (pacs.008).
- [`camt053-mcp`][camt053-mcp] — bank-to-customer statements (camt.053/052).
- [`acmt001-mcp`][acmt001-mcp] — account opening instructions (acmt.001).
- [`bankstatementparser-mcp`][bsp-mcp] — MT94x / BAI2 / OFX statement parsing.

## Development

```sh
git clone https://github.com/sebastienrousseau/reconcile-mcp
cd reconcile-mcp
python -m venv .venv && . .venv/bin/activate
pip install -e . && pip install pytest pytest-cov ruff black mypy
pytest                      # 100% branch coverage gate
ruff check reconcile_mcp tests && black --check reconcile_mcp tests && mypy reconcile_mcp
```

## Licence

Licensed under the [Apache License, Version 2.0](LICENSE).

---

`mcp-name: io.github.sebastienrousseau/reconcile-mcp`

[mcp]: https://modelcontextprotocol.io
[pain001-mcp]: https://github.com/sebastienrousseau/pain001-mcp
[pacs008-mcp]: https://github.com/sebastienrousseau/pacs008-mcp
[camt053-mcp]: https://github.com/sebastienrousseau/camt053-mcp
[acmt001-mcp]: https://github.com/sebastienrousseau/acmt001-mcp
[bsp-mcp]: https://github.com/sebastienrousseau/bankstatementparser-mcp

