The first time I ran it on test data, the agent flagged a payout that arrived $12.50 short.
Twelve dollars and fifty cents. No finance team I know would open a ticket for that. Which is exactly the problem.
I've co-owned a payments P&L across 10+ markets and worked on three-way reconciliation and payout partners. The expensive work in payments operations isn't the big incident everyone sees. It's the small leaks nobody has time to chase: an order captured twice, a fee a few cents above contract, a payout that never arrived, a chargeback due in two days that's still sitting in a dashboard.
That work is rules plus judgment, spread across three systems that never quite agree: your order ledger, your payment provider and your bank. It's a perfect fit for an agent. It's also the worst possible place to let an agent act on its own.
So I built payments-ops-mcp, an open-source MCP server that does a payments ops analyst's morning. This is how it works, and why I built it the way I did.
The user journey
The person I designed for is a controller or a one-person payments ops team at a Series A–C company. They have real volume across a payment provider, a bank and their own ledger, but no budget for an enterprise reconciliation platform. They live in spreadsheets.
Setting it up takes minutes, not an IT project.
- They share their payment provider's pricing schedule and say "set this up as my fee terms". The agent reads it and pulls out the fee, the chargeback fee and the payout delay, quoting the clause each one came from.
- The server checks every value before they see it: does that quote really appear in the document, and does it contain that number? A 29% fee, or 2.9% written as 0.029, is rejected outright.
- They confirm. Only then is anything saved, as a version with an effective date.
- They share the exports they already download every week: orders from their shop, the provider's transaction report, the bank statement, the disputes list. For a new layout, the agent shows the column mapping it suggests and a few converted rows. They agree once. Every later file in that layout imports in one step.
Then, every morning: "Run my payments ops review." The agent reconciles all three systems and shows the match rate and the money sitting in breaks. It explains the biggest breaks and triages every chargeback due in the next few days. It ends with a short to-do list that marks what only a person can approve.
On the test data, it found all seven problems I'd planted. It also linked a duplicate charge to the "duplicate processing" chargeback the customer had already filed on the same order. Two different teams would normally see those separately.

The morning review on the sample data. Run it yourself from the repo with python scripts/demo.py review.
How it's built
It's a small Python MCP server that runs on your own machine. Any MCP client can use it, such as Claude Desktop or Claude Code. It has three layers.
Only the onboarding tools write, and only to the local workspace after you confirm. No tool issues a refund or submits anything to a card network.
A thin tool layer. Thirteen tools in two groups. Six analysis tools are read-only: reconcile, explain a break, list disputes, triage a dispute, draft an evidence pack, check a refund. Seven onboarding tools handle setup. Three only read (preview a file, list contract versions, list what's loaded), and four write to a local workspace folder (propose and save contract terms, save a column mapping, import a file). None of them move money or submit anything to a card network.
A deterministic core. This is where every number comes from. Reconciliation runs at two levels. First, each order in the ledger is matched to its capture at the payment provider: is it there, is it the same amount, was it captured twice, is the fee what the contract says for that date? Then each payout is matched to the bank deposit that should settle it. A payout that hasn't arrived counts as in transit until the contract's payout delay has passed, and only then as missing. Amounts are whole numbers of cents internally, so nothing ever does floating-point maths on money.
A workspace. Plain JSON files in a local folder: imported transactions, contract versions, saved column mappings. Until you import your own data, it runs on fictional sample data, and it never mixes the two.
The model's job sits on top: read the contract, guess which column is which, decide which tools to call, connect the findings and write the explanation. It never calculates a match rate or decides whether a fee is wrong.
There are 50 tests. One of them imports realistic exports into an empty workspace and checks the results are identical to the built-in sample. Those exports are a Stripe-style balance report, a shop export with day-first dates, a disputes file and an ISO 20022 bank statement.

The full test suite, including the import test that must reproduce the sample results exactly.
The decisions
Every choice below had an obvious alternative. Here's what I picked and what it cost.
An MCP server, not another dashboard. The default would have been a web app with a reconciliation screen. But finance teams don't need another tab. They need the work done inside the assistant they already use, joined up with their email, docs and calendar. An MCP server is that: one integration, any agent. The trade-off is that there's no polished UI to demo. I decided the agent's to-do list is the UI.
Code calculates, the model explains. I could have handed the model three CSVs and asked it to reconcile them. It would mostly work, which is the problem with money. "Mostly" means a match rate that changes between runs and a fee check nobody can audit. So every number comes from plain, tested code that gives the same answer every time. The model does what it's actually good at: reading, prioritising, connecting and writing. I wrote more about borrowing this kind of discipline from payments in Payments Taught Me How to Build Agents.
Read-only first, with approval as the product. An agent that issues refunds on its own is a better demo and a much worse product, because no CFO will turn it on. So the analysis tools are read-only and say so in their metadata. Every recommendation also states who has to approve it. A refund check returns executes_refund: false. A dispute triage returns requires_human_decision: true. Trust is what gets an agent past a finance team, so I treated it as the feature, not a constraint. That idea is the subject of Approval Is a Product Feature.
Extraction is checked, not trusted. Having the model read a contract is the right call, because that's what it's good at. Trusting what it read is not. So every value must come with a verbatim quote, and the server checks the quote exists in the document and contains the number before anyone is asked to confirm. If the model misreads a $15 chargeback fee as $10, the person sees "quote not found" instead of a confident wrong answer.

The misread chargeback fee, caught before anyone is asked to confirm it.
Contracts are versioned, not a config file. A single fee setting is simpler and wrong the first time pricing changes. With versions and effective dates, October's transactions are checked against October's prices. Every fee break also names the version it used, so it holds up in a conversation with the provider.
Exports first, live connectors later. Live API connectors sound more impressive. But each one is a separate integration, a security review and a reason for IT to say "next quarter". Every provider and bank already lets you download a file. So I started with the files people already have, plus a mapping you confirm once. That makes onboarding a ten-minute task instead of a project, and it works with any provider on day one. Connectors come next, for teams who are already convinced.
What it doesn't do yet
It handles one currency and one payment provider. It stores the international card surcharge and the rolling reserve but doesn't apply them yet, because exports rarely say where a card was issued. It doesn't import dispute evidence such as 3-D Secure results or proof of delivery from CSV files. Win likelihood on disputes is a transparent heuristic, not a trained model.
Next on the list are three things. First, a refund tool that pauses and asks the person to confirm inside the agent before anything executes. Second, read-only live connectors. Third, an eval set of the breaks that catch people out (partial captures, FX rounding, refunds netted from payouts), so every change to the matching logic is measured, not guessed.
The part I keep coming back to
The most useful line of code in this project isn't the matching logic. It's executes_refund: false.
It's what lets a finance team say yes. Most agent demos compete on how much the agent can do on its own. Near money, the products that get switched on will be the ones that are clearest about what the agent can't do, and that prove it.
The code, sample data and a full walkthrough are on GitHub: jana-han/payments-ops-mcp. If you run payments or finance ops, I'd like to know which break eats most of your morning.