ledger-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ledger-mcpWhat did I spend on software last quarter? Show the rows behind it."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ledger-mcp
Deterministic finance tools for AI agents over your own CSV bank exports. Same answer twice, with the rows behind every number. Local and read-only.
ledger-mcp is a Model Context Protocol server that runs on your machine over stdio. Point it at a folder of CSV exports from your bank or card provider and your MCP client gets five tools: import_csv, list_transactions, totals, cashflow and explain.
The engine does the math; the model only explains it. Every number is computed with exact decimal arithmetic, is byte-for-byte reproducible for the same files, and cites the file and row numbers it came from.
Contents
Related MCP server: monter-mcp
Quick start
Requirements: Node.js 22.12 or newer and pnpm 10.
git clone https://github.com/folioflux/ledger-mcp.git
cd ledger-mcp
pnpm install
pnpm buildTry it on the synthetic sample files in fixtures/:
pnpm demoThis starts the built server, speaks MCP to it over stdio and prints the answers: a February cashflow figure and the exact rows behind it.
To use your own data, put your CSV exports in one folder (for example ~/finance/exports) and run:
node packages/ledger-mcp/dist/cli.js --dir ~/finance/exportsThe server waits for an MCP client on stdin/stdout, so in practice you let your client start it, as shown below.
Connect a client
Replace /path/to/ledger-mcp with the folder you cloned and /path/to/exports with your CSV folder. Use absolute paths.
Claude Desktop
Add this to claude_desktop_config.json (Settings → Developer → Edit Config) and restart Claude Desktop:
{
"mcpServers": {
"ledger": {
"command": "node",
"args": ["/path/to/ledger-mcp/packages/ledger-mcp/dist/cli.js", "--dir", "/path/to/exports"]
}
}
}Claude Code
claude mcp add ledger -- node /path/to/ledger-mcp/packages/ledger-mcp/dist/cli.js --dir /path/to/exportsCursor
Add this to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"ledger": {
"command": "node",
"args": ["/path/to/ledger-mcp/packages/ledger-mcp/dist/cli.js", "--dir", "/path/to/exports"]
}
}
}Codex
codex mcp add ledger -- node /path/to/ledger-mcp/packages/ledger-mcp/dist/cli.js --dir /path/to/exportsor add this to ~/.codex/config.toml:
[mcp_servers.ledger]
command = "node"
args = ["/path/to/ledger-mcp/packages/ledger-mcp/dist/cli.js", "--dir", "/path/to/exports"]Then ask things like "What was my cashflow in February?" or "Show me the rows behind the Software total."
How it works
On every call the server reads the
*.csvfiles directly inside--dir.For each file it decides the encoding, delimiter, columns, date format and decimal separator, in this order of precedence: a mapping you saved with
import_csv, then plain detection rules. When anything is ambiguous (for example03/04/2026could be 3 April or 4 March) it does not guess. The file is reported with statusneeds_mappingand the open questions, and is left out of the numbers until you answer them.Rows become normalized transactions (see
@folioflux/transaction-schema) with exact decimal amounts and provenance: source file, SHA-256 of the file, row number and mapping id.Query tools filter, group and sum those transactions and return decimal strings, the rows they used, and an
input_hashthat identifies the exact inputs.
The repository has three packages:
Package | What it is |
JSON Schema and TypeScript types for a normalized transaction | |
Pure CSV parsing, mapping, querying and explanation (no I/O) | |
The MCP server and command line entry point |
Tool reference
All tools return a single JSON text block. Amounts are decimal strings with at least two fraction digits: money in is positive, money out is negative. Different currencies are never added together; groups are always per currency.
Every query result starts with input_hash and also includes:
files: one entry per CSV file withstatus(ok,needs_mappingorerror),mapping_id, transaction and row error counts, and a message when the file was not used.row_errors(first 50) androw_error_count: rows that could not be read, with file, row number, column and value. They are never counted silently.
Row numbers count data rows from 1, starting after the header. Blank lines are skipped but still counted, so a row number always points at the same line of the file.
Errors are returned as tool errors with {"error": {"code": "...", "message": "..."}}.
Shared filters
list_transactions, totals and cashflow accept:
Argument | Type | Meaning |
|
| First date to include (inclusive) |
|
| Last date to include (inclusive) |
| string | Case-insensitive substring of description, merchant or category |
| string | Exact value of the category column |
| string | Exact account label |
| string | Three-letter ISO 4217 code |
|
| Money in (amount > 0), money out (amount < 0) or both. Not on |
import_csv
Reads one file and shows how it will be interpreted: the detected or saved mapping, any open questions with candidate answers, row errors and a preview of normalized transactions. With save: true it writes the mapping to .ledger-mcp/mappings/ so the query tools use it.
Argument | Type | Meaning |
| string | Plain name of a |
| object | Overrides: |
| boolean | Save the resulting mapping. Refused while there are open questions |
| integer | Transactions to preview (default 5) |
The result includes status (ok or needs_mapping), issues, mapping, mapping_id, header, sample rows when a mapping is needed, saved, mapping_path and a next_step hint.
list_transactions
Lists matching transactions in a fixed order (date, file name, row number) with limit (1 to 500, default 50) and offset paging. Returns total_count, next_offset and the transactions with their provenance.
totals
Sums amounts by group_by: "month" or group_by: "category", per currency. The category is the value of the category column in your CSV; rows without one are grouped under null, listed last. Each group has key, currency, total, count, sources (file and row numbers) and a ref for explain.
cashflow
Money in, money out and net per calendar month and currency: inflow (sum of positive amounts), outflow (sum of negative amounts, so zero or negative) and net. Each month has sources and a refs object with one ref per figure.
explain
Shows the exact rows behind a number, with the original CSV cells, the mapping and the file hashes, and recomputes the sum from those rows.
Argument | Type | Meaning |
| string | A |
| array | Or explicit rows: |
| string | The |
The result has figure (tool, query, group, field, currency and reported value), sums per currency, sum_matches_figure, input_hash_matches, rows and files.
A ref is self-contained: it encodes the query and the figure, so explain recomputes the figure from the current files. If the files changed, input_hash_matches is false, and if the figure no longer exists you get a NOT_FOUND error.
Column mappings
Detection recognizes common English header names, for example Date, Transaction Date, Description, Payee, Amount, Money In / Money Out, Debit / Credit, Currency, Category and Account. A file can have either one signed amount column or a debit/credit pair (read as amounts: credit minus debit).
Supported formats:
Encodings: UTF-8 (with or without BOM) and Windows-1252.
Delimiters:
,;tab|.Dates:
YYYY-MM-DD(a time suffix is ignored),YYYY/MM/DD,DD.MM.YYYY,DD/MM/YYYY,MM/DD/YYYY,DD-MM-YYYY.Amounts:
.or,decimal separator, thousands grouping with,.spaces or', a leading or trailing minus, parentheses for negatives, and a currency symbol or code next to the number.
If your headers use other names or another language, map them once:
{
"file": "my-bank.csv",
"mapping": {
"columns": { "date": "Buchungstag", "description": "Verwendungszweck", "amount": "Betrag" },
"date_format": "DD.MM.YYYY",
"decimal_separator": ",",
"default_currency": "EUR"
},
"save": true
}Saved mappings are plain JSON files named after the SHA-256 of the header row, in <dir>/.ledger-mcp/mappings/. Every file with the same header layout uses the same mapping, so new monthly exports from the same bank work without further steps. You can review, edit or delete these files; the server validates them on every read.
Determinism
For the same CSV files and mappings, every tool returns the same bytes:
Amounts use exact decimal arithmetic, never floating point. A lint rule and a test keep
parseFloat,Number(), locale functions, the clock and random numbers out of the core.Sorting compares strings by code units, never by locale, and transactions are always ordered by date, file name and row number. The order files are read in does not matter.
Output JSON has a fixed key order, and nothing depends on the time zone, the locale or the current date.
input_hashis the SHA-256 of the file names, file hashes and mapping ids. Change one byte of one file and it changes.
The test suite checks this with repeated runs, shuffled inputs, golden files compared byte for byte, and the built server run under several time zones and locales.
Security
Local. The server runs on your machine and talks to your MCP client over stdio only. It opens no ports and makes no network requests; the test suite runs the built server with networking disabled and fails on any attempt.
Read-only for your data. CSV files are only read. The only write is a mapping file under
<dir>/.ledger-mcp/mappings/, and only when you callimport_csvwithsave: true.Confined to one folder. Only regular
*.csvfiles directly inside--dirare read: no subfolders, no symlinks, no paths in file names, no hidden files. Files over 50 MiB and folders with more than 1000 CSV files are refused.No telemetry, no accounts and no keys.
Your MCP client still sends tool results to whichever model it uses. Point --dir at a folder that contains only the exports you are willing to share with it.
Limitations
Header detection knows English names only. Other layouts work after you save a mapping.
UTF-16 files are not supported; re-save them as UTF-8.
Duplicate rows are not detected. If two exports overlap in time, the overlapping transactions are counted twice, so export non-overlapping periods.
Amounts in different currencies are kept apart and never converted.
The folder is re-read on every call. This keeps answers in step with the files and suits personal and small-business volumes.
Not yet published to a package registry; run it from a clone as shown above.
Development
pnpm install
pnpm check # format check, build, lint, typecheck, tests
pnpm test # tests only (the stdio test needs a prior build)
pnpm golden:update # rewrite fixtures/expected after an intended output change
pnpm demo # scripted MCP session against the built serverAll sample data in this repository is synthetic. Please never commit real bank exports.
Contributing
Bug reports and focused pull requests are welcome. See CONTRIBUTING.md and the Code of Conduct. Please report security problems privately, as described in SECURITY.md.
License
Released under the MIT License.
About
Made by FolioFlux. Questions: hello@folioflux.com. Security reports: security@folioflux.com.
Not financial, tax or legal advice.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query your CSVs and Google Sheets from any agent: filter, group and total with exact results.
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Personal finance for AI agents — onboard, import statements, categorize & budget over MCP.
Deterministic signed verification of numeric & financial claims for AI agents & spreadsheets.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server for deterministic numeric verification and spreadsheet auditing. Enables AI agents to verify math/finance claims, audit rows, and evaluate covenant rules with signed, independently checkable receipts.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables local analysis of personal finance data from a Monter JSON export, answering questions about spending, subscriptions, debts, and more via MCP-capable assistants without any cloud servers.MIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to run SQL analytics directly on CSV files in place via MCP, avoiding imports and token-heavy data pasting. It provides read-only querying, schema inspection, and file listing while keeping data local.30MIT
- AlicenseNot gradedqualityBmaintenanceEnables an AI assistant to manage a local-first household ledger through MCP: accounts, categorised entries, monthly budgets, bank CSV imports, transfers, and reports stored in a single SQLite file.MIT