Home · Guides

Client deposits and retainers from chat, applied to your real invoices

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.

Install it beside the invoice server

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 measured thing: the deposit write path found a silent bug in the invoice server

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, and which tool runs

What you sayTool
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

A deposit pays out at most what it still holds

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.

A refund is not a payment

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.

One statement, one currency

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 and Pro

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.

Questions

Does applying a deposit actually change the invoice?

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.

Can a deposit in one currency pay an invoice in another?

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.

What stops a deposit being applied twice?

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.

Is a refund recorded on the invoice?

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.

What does the free tier actually limit?

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.

Where is the data kept?

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.

Related

All MCP servers and prices · All guides · Buy the bundle $39