A deposit is the client's money sitting on your account. A landlord holds one against damage, an agency takes one before it starts, a trade asks for half up front. It is not revenue and it is not a payment on an invoice until you make it one, and in between somebody has to be able to answer a plain question: how much of theirs are you holding, in which currency, and since when. The MCP Deposits server answers that against the invoices and clients the MCP Invoice server already holds, on your machine, with no network call anywhere in it.
claude mcp add invoice -- npx -y @theluckystrike/mcp-invoice
claude mcp add deposits -- npx -y @theluckystrike/mcp-deposits
Cursor, in .cursor/mcp.json, and Claude Desktop with the same block under
claude_desktop_config.json:
{
"mcpServers": {
"invoice": { "command": "npx", "args": ["-y", "@theluckystrike/mcp-invoice"] },
"deposits": { "command": "npx", "args": ["-y", "@theluckystrike/mcp-deposits"] }
}
}
Both read one data directory and one business profile, so your name, address, VAT id and default currency
are set once. Deposits holds no copy of the money, currency or client code: it imports
currencyDecimals, formatMoney, findClient, getInvoices
and setInvoices from the invoice engine, and the A4 page from
MCP Billing Docs, which is why a deposit statement agrees with the invoice and
the credit note beside it to the minor unit.
The invoice server exports no payment function. Its own invoice_mark_paid tool sets three
fields under the invoice lock, and on 2026-09-05, when this server was built against it, the important word
was sets. Measured against servers/invoice/dist/index.js as it stood that morning, on a
EUR 1,000.00 invoice:
invoice_mark_paid { invoice: "INV-2026-0001", amount: 200 } -> "balance due EUR 800.00"
invoice_mark_paid { invoice: "INV-2026-0001", amount: 300 } -> "balance due EUR 700.00"
paid_minor: 30000
The EUR 200.00 bank transfer that actually arrived was gone from the record. Nothing errored and nothing warned; the only visible trace was a number EUR 200.00 too high, on the line the client gets chased for.
deposit_apply writes the same three fields on the same record, because there is no
recordPayment to call, but it adds to paid_minor instead of assigning it. On a real
client run the same day, an invoice seeded with a EUR 200.00 transfer already on it ended at
paid_minor 50000 after a EUR 300.00 deposit was applied, and the tool reported paid EUR 500.00
of EUR 1,230.00 with EUR 730.00 due.
The invoice server was fixed the same day. invoice_mark_paid now adds too,
refuses a payment that would overpay the open balance, and appends to a payments list, so the
sequence above leaves EUR 500.00 received. That is the better outcome and it is recorded here rather than
quietly dropped: the paragraph above is a dated measurement of how the tool behaved before the fix, not a
claim about it now.
The general form is what survives, and it is worth carrying to any two servers that share a store: matching field names is not the contract. The arithmetic on them is, and it is only visible by reading the owning server's write path. A review that checked the schema would have passed the version that assigns. Nothing found this except writing a second server against the same field.
| What you say | Tool |
|---|---|
| Record a 500 euro security deposit from Nordic Print, received today. | deposit_record |
| Apply 300 of that deposit to INV-2026-0001. | deposit_apply |
| How much are we holding for Nordic Print, and since when? | deposit_balance |
| Refund the rest, sent back by bank transfer. | deposit_refund |
| Send them a statement of their deposit. | deposit_statement_text |
| What has sat unapplied for more than ninety days? | deposits_report |
Held is received, less everything already applied, less everything already refunded. Every application is
also capped by the invoice's own open balance, total_minor - paid_minor. Both checks and the
write happen in one critical section under both locks, deposits first and then invoice, the same order the
quotes, recurring and billing-docs servers take, so no two processes in this estate can deadlock and no two
can each see room and both take it. Ten concurrent EUR 200.00 applications against a EUR 500.00 deposit
store exactly two and refuse exactly eight.
The refusals name the number rather than the rule. Applying EUR 200.00 when EUR 100.00 is left says so; applying EUR 200.00 to an invoice that owes EUR 100.00 says the application "would show the invoice overpaid and leave the difference owed to the client twice"; and a EUR deposit against a USD invoice is refused with both currencies named and the way out, which is to refund it and record it again in the invoice's currency. There is no exchange rate anywhere in this server, so there is no rate to be silently wrong.
deposit_refund does not touch the invoice server at all. Giving a client their own money back
is not the settlement of a bill, and writing it as one would show an invoice paid that nobody paid. The
refund is a movement on the deposit, and the invoice stays exactly as it was.
For the same reason the stored status is derived from the movement list every time a movement
is written, never taken from the caller: held while anything is still held, otherwise
applied if any of it went to an invoice and refunded if all of it went back. A
stored status that is allowed to drift from the movements is how a deposit comes to look returned while the
money is still on your books.
A client holding EUR and USD has two balances, and adding them would be a made-up number. So
deposit_balance returns one row per currency and never a total across them, and
deposit_statement_text asks which currency you mean, naming both, rather than guessing.
Free records 5 deposits a calendar month, counted by the date the money arrived. Applying to invoices, refunds, lists, balances and the text statement are unlimited on every tier, deliberately: a cap that trapped a client's deposit would be a limit on their money rather than on yours. Pro ($19 one-time, or $39 for the bundle) removes the recording cap and adds the A4 statement PDF with your logo and the held, oldest-held and unapplied report.
Yes. deposit_apply writes paid_minor, paid_date and status onto the invoice record through the invoice engine's own store, under the invoice lock, so invoice_list and overdue_report stop chasing money you already hold. The one difference from invoice_mark_paid is that it adds to paid_minor rather than setting it, so a payment that arrived earlier is not erased.
No, and it is refused by name rather than converted. The message gives both currencies and the way out: refund the deposit and record it again in the invoice's currency. There is no exchange rate anywhere in this server, so there is no rate that can be silently wrong.
Held is received less applied less refunded, and the check and the write are one critical section under both locks. Ten concurrent applications of EUR 200.00 against a EUR 500.00 deposit store exactly two and refuse the other eight; the invoice ends at EUR 400.00 paid, never more than was held.
No. Giving a client their own money back is not a payment of a bill, so deposit_refund leaves the invoice untouched. It is a movement on the deposit, and it changes the deposit's derived status once nothing is left held.
Only deposit_record, at 5 a calendar month by received date. Applying, refunding, listing, balances and the text statement are unlimited on every tier. A deposit received in a different month is not blocked by this month's five. The statement PDF and deposits_report are Pro.
Plain JSON under ~/.local/share/mcp-servers/deposits/, or $XDG_DATA_HOME if you set it, with the invoices in the invoice server's own directory beside it. Writes are atomic and locked. A corrupt file is moved aside byte-for-byte rather than overwritten, and no fresh file is silently written in its place.
All MCP servers and prices · All guides · Buy the bundle $39