| 6000 | 2,400.00 | |
| 1020 | 2,400.00 |
MCP meets the ledger: can an AI agent keep the books?
Published Updated 9 min read
The short answer
Yes, if the ledger enforces the rules rather than the agent. In TILL, an open-source Swiss ERP, an agent posts through the MCP verb post_entry. Every call carries an idempotency key, so a retry never double-counts. By default the write becomes a proposal a person approves. The engine refuses unbalanced entries and locked periods, and a posted entry is only ever reversed, never edited.
Update, 14.10.2026: TILL is now a public beta (version 1.0.0-beta.1), and Nomadik GmbH keeps its own books in it. Where this article calls TILL pre-alpha, it describes the state when it was written. If you switch, keep your current program running until the figures match.
Why an agent needs a ledger that says no
A language model retries a call when no answer comes back. It misreads an account, rounds an amount, or books the same invoice twice because it appeared in two emails. A better prompt makes that rarer, not impossible. So the question is not whether the agent makes mistakes. It is which mistakes the system will accept.
Bookkeeping answered that question centuries ago: double entry, source documents, no erasures, corrections by a counter-entry. A ledger built for agents needs the same rules, enforced by the machine instead of by instruction. This article follows one real call to show how TILL does it.
MCP in one paragraph
The Model Context Protocol (MCP) is an open protocol that lets an AI client call tools on a server. The server describes each tool with a name, an input schema and a description; the agent picks one and sends the arguments. In TILL the MCP server is not an add-on, it is the interface: till mcp talks to a local agent over stdio, and till serve exposes the same verbs over HTTP on localhost. The Studio, the interface for people, uses the same verbs. More on the agents page.
One call, followed all the way down
You tell your agent: “Book September’s rent, CHF 2,400.00, paid from the bank account.” The agent finds accounts 6000 (rent and utilities for business premises) and 1020 (bank) with list_accounts, then calls post_entry:
{
"workspaceId": "ws_demo",
"date": "2026-09-30",
"description": "Rent September 2026",
"source": "agent",
"idempotencyKey": "rent-2026-09",
"lines": [
{ "account": "<id of 6000>", "debit": 240000 },
{ "account": "<id of 1020>", "credit": 240000 }
]
}list_accounts. Rent is exempt from Swiss VAT unless the landlord opts to tax it (MWSTG Art. 21 para. 2 no. 21, Art. 22). This one has not, so there is no tax line.Every agent call over MCP goes through the same gate. First the permission: does this role hold the right to post? Then the approval dial. At “ask”, which applies unless someone changed it, TILL writes nothing to the ledger. The agent gets a proposal back:
{ "ok": true, "drafted": true, "actionId": "…", "status": "pending",
"dialCapability": "post", "reason": "dial_ask", "replayed": false }The proposal appears in the Studio under Agent, Proposals. When a person approves it, TILL replays the stored call on that person’s behalf, re-checks their permission, and reuses the same idempotency key. Only then does the engine check the entry itself and append it to the journal.
Rule 1: every posting carries a key
Networks lose responses. An agent that sees no answer tries again. Without protection, the rent is now in the journal twice. That is why idempotencyKey is a required field on post_entry.
The uniqueness lives in the database, not just in code: an idempotency table has a primary key of workspace, verb and key. The result and the key are written in the same transaction. When the key comes back, TILL answers with the stored result before any check that depends on the state of the books. Proposals work the same way: while one is pending, the same key returns the same proposal, never a second one.
The conformance suite calls every write verb twice with identical input and requires that the second call changes nothing. post_entry is also double-posted over the real MCP wire and must leave exactly one entry.
Rule 2: the agent proposes, a person decides
The approval dial is set per capability. At “ask”, every write becomes a proposal. At “auto”, the agent posts directly, but only if a person set that level. Marking a VAT period as filed has a strong default: no bulk setting unlocks it, only an explicit grant for that one capability.
- The agent cannot approve its own proposals (
cannot_self_approve). - It cannot raise its own dial (
cannot_self_grant). - The built-in
agentrole may post and draft, but it cannot send documents. - Every call on the agent seat leaves a trace: executed, drafted or denied, with the reason and the error code.
That is the default, not a mandate. Once you trust an agent after a few weeks, you set single capabilities to “auto” and keep the rest at “ask”.
Rule 3: what does not balance is not posted
Before anything is written, the engine checks the whole entry. A refusal is not a protocol error but an ordinary result with ok: false and a code the agent can read and act on:
| Code | Meaning |
|---|---|
unbalanced | Debits and credits differ. The sum is taken in whole Rappen, with no floating point. |
invalid_account | The account does not exist in this workspace. |
period_locked | The period is locked, for example after a filed VAT return. |
unknown_tax_code | The VAT code is not set up. |
vat_trace_unreconciled | The VAT details on the lines do not match the entry. |
needs_fx_rate | A foreign-currency entry with no admissible rate. |
Every verb that touches a set of books requires a workspaceId, and every query is scoped to that workspace. An agent that serves several companies cannot use one company’s accounts in another’s books.
Rule 4: posted means posted
In a real set of books, a posted entry cannot be edited or deleted. The database carries triggers that abort any update or delete of a posted entry or line with posted_immutable. The correction is a reversal: reverse_entry posts a mirror entry with the sides swapped.
- Each entry can be reversed exactly once. A unique index in the database refuses a second reversal.
- Entries that belong to a document, such as a payment, are reversed through that document’s verb, not directly.
- A period can be locked. After a filed VAT return or the year-end close, the lock is sealed.
What this does not solve
These rules stop duplicate, unbalanced and rewritten entries. They do not stop an agent from picking the wrong account. Rent booked to 6500 instead of 6000 balances, passes every check, and is posted once approved. That is why approval is the default, and why a proposal shows the lines, not just a summary.
The HTTP door of till serve is bound to localhost and has no authentication of its own. The seat a client gets there is whatever the client declares itself to be; only till mcp over stdio pins the agent seat. So the HTTP door does not belong on a network, and an agent should be connected over stdio. And TILL is pre-alpha: the code is public, but it is not ready for real books. More on the limits on the security page.
A checklist for any accounting MCP server
Whoever connects an agent to an accounting system, any system, can ask these questions:
- Does every write tool that can double-count require an idempotency key, and is uniqueness enforced in the database?
- Do refusals come back as a readable result with a code, so the agent can correct itself?
- Are posted entries immutable below the application, or only in the interface?
- Does an agent’s write become a proposal a person approves, by default?
- Can the agent raise its own permissions or approve its own proposals?
- Does every call leave a trace with its outcome and reason?
- Are amounts integers in the smallest unit rather than floating-point numbers?
- Is every call scoped to exactly one workspace?
Questions people ask
Can ChatGPT or Claude do my bookkeeping?
A language model does not keep books; it operates a program that does. Over MCP, an agent such as Claude can call the tools of an accounting system. Whether that is safe depends on what the accounting system refuses: duplicate calls, unbalanced entries, edits to posted entries.
What is an MCP server for accounting?
A program that offers the functions of an accounting system as tools over the Model Context Protocol, such as “post an entry” or “get the trial balance”. An AI client such as Claude Desktop connects to it and calls the tools. In TILL, the MCP server is the primary interface.
What happens if the agent books the same invoice twice?
With the same idempotency key, TILL returns the original result and writes nothing new. With a different key it would be two calls. That is why a good agent derives the key from the source document, say the supplier and the invoice number, and not from the clock.
Can an AI agent delete or change a booking?
Not in TILL. Posted entries are locked against update and delete in the database. Corrections are reversals, which post a counter-entry. Both entries stay visible.
Does the agent post without my approval?
Not by default. Almost every posting the agent makes becomes a proposal that you approve or reject in the Studio. You can set single capabilities to “auto”. The agent cannot do that itself.
Can I use TILL for my real books yet?
Not yet. TILL is pre-alpha, open source under MIT and public on GitHub. The engine is far along, but it is not ready to keep real books.