ledger-mcp
Click on "Install 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-mcpCross-examine my rent ledger for any discrepancies with my lease."
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.
LedgerMCP
Keep an eye on your rent ledger — and cross-examine it against your lease — through your AI assistant.
LedgerMCP is a Model Context Protocol server built for renters. Import your property/rent-portal ledger (CSV, TSV, Excel) and your lease (PDF), and LedgerMCP gives an AI assistant a set of deterministic tools to answer questions like:
"Am I being charged the right rent every month?"
"Is this 'Valet Trash' fee actually in my lease?"
"Did they charge me the late fee my lease specifies — or more?"
"What have I paid this year, and what's my current balance?"
The assistant answers by calling tools that do exact arithmetic in Python
(every amount is a Decimal,
never a float) and by comparing your ledger line-by-line to your lease —
instead of eyeballing a table in its context window and guessing.
Why
Ask an LLM to "total my fees for Q2" or "check my rent against my lease" from a pasted statement and you get plausible, confidently-wrong numbers. LedgerMCP moves the numbers and the comparisons out of the prompt and behind tools:
The model decides what to ask (
check_rent_charges,find_unexpected_charges, …).LedgerMCP decides what the answer is, deterministically, in Decimal — and shows its work by quoting the exact lease text it relied on.
Related MCP server: Estaite Solutions
Features
Renter-first workflow — import a rent ledger and a lease, then cross-examine.
Lease parsing (PDF) — heuristically extracts rent, deposit, term dates, due day, late-fee policy, recurring fees and parties, each with a confidence level and the source excerpt. The full lease text is retained so the assistant can read anything the heuristics miss.
Deterministic cross-examination — rent checks, unexpected-charge audits, deposit and late-fee comparisons, all exact.
Pluggable ledger parsers — CSV/TSV and the Excel "Full Ledger" rent-portal export out of the box; add QuickBooks/OFX/QIF/Xero by writing one small class.
SQLite storage with lossless Decimal (amounts stored as integer cents).
Classic analytics too — balances, category/monthly rollups, income statement, balance sheet.
MCP server (FastMCP) + a Typer CLI over one shared service layer.
Idempotent ledger imports, high test coverage, Ruff, Pyright, GitHub Actions CI.
MCP tools
Lease cross-examination
Tool | Description |
| One-call cross-examination: lease terms + every check + human-readable |
| Compare each month's rent charges to the lease's monthly rent. |
| Classify every charge category as expected / referenced / mismatch / unexpected. |
| Compare the deposit charged to the lease deposit. |
| Compare late fees charged to the lease late-fee policy. |
| Extracted lease terms, each with confidence + source excerpt. |
| Read the raw lease, or just the lines matching a query (pet rules, subletting…). |
Ledger analytics
Tool | Description |
| Filter by date, category, type, account, source, text, amount range. |
| Running balance for an account or the whole ledger, optionally as-of a date. |
| Net / inflow / outflow totals for a filtered set. |
| Spending grouped by calendar month. |
| Per-category rollup, ranked by spend. |
| Accrual-basis P&L for a period. |
| Assets / liabilities / equity as of a date. |
| The chart of accounts and each account's type. |
Installation
LedgerMCP uses uv.
git clone https://github.com/luke-nielsen/ledger-mcp.git
cd ledger-mcp
uv syncQuickstart
# 1. Import your rent ledger
uv run ledger-mcp import examples/sample_ledger.csv --db ledger.db
# 2. Import your lease
uv run ledger-mcp import-lease examples/sample_lease.pdf --db ledger.db
# 3. See what the lease says
uv run ledger-mcp lease --db ledger.db
# 4. Cross-examine ledger vs lease
uv run ledger-mcp lease-report --db ledger.dbThe bundled example intentionally shows a clean rent match, a late-fee discrepancy (lease says $75, ledger charged $50), and coverage gaps (a deposit and pet rent that were never charged):
LEASE vs LEDGER
Findings:
• 'Late Fee' charged $50.00 but lease specifies $75.00.
• Lease items not seen in the ledger: Security Deposit, Pet Rent.
• Late fees charged $50.00 but lease late fee is $75.00.
Rent check:
2026-04 charged $1,450.00 expected $1,450.00 [match]
2026-05 charged $1,450.00 expected $1,450.00 [match]
2026-06 charged $1,450.00 expected $1,450.00 [match]You can also import a real Excel rent-portal statement and your real lease:
uv run ledger-mcp import "full_ledger.xlsx" --db ledger.db
uv run ledger-mcp import-lease "my_lease.pdf" --db ledger.dbUse it with an AI assistant (the point)
Run the MCP server and point any MCP client at it. For Claude Desktop, add to
claude_desktop_config.json:
{
"mcpServers": {
"ledger": {
"command": "uv",
"args": ["run", "ledger-mcp", "serve"],
"cwd": "/absolute/path/to/ledger-mcp",
"env": { "LEDGER_MCP_DB": "/absolute/path/to/ledger.db" }
}
}
}Then ask: "Does my ledger match my lease?" — the assistant calls
lease_ledger_report, relays the flags, and can drill in with check_rent_charges
or get_lease_text to quote the exact clause.
Programmatic use
from ledger_mcp import LedgerService, Settings
service = LedgerService.from_settings(Settings.from_env(db_path="ledger.db"))
service.import_file("examples/sample_ledger.csv")
service.import_lease("examples/sample_lease.pdf")
report = service.lease_report()
for flag in report.flags:
print("•", flag)How lease cross-examination works
Lease PDFs are unstructured and vary wildly, so LedgerMCP splits the problem:
Extraction is heuristic but honest. Regex/keyword rules pull the common fields; each extracted value carries a
Confidence(high/medium/low) and the verbatimexcerptit came from. Whatever isn't matched is simply left unset.The full lease text is retained, so a client can read anything the heuristics miss via
get_lease_text(pet policies, subletting, maintenance).The comparisons are deterministic. Every check below is exact Decimal arithmetic over stored data — the terms may be fuzzy, but the math isn't:
Check | What it does |
Rent | Sums each month's rent charges and compares to the lease's monthly rent (match / over / under / missing). |
Charge audit | Matches each charge category to base rent, the deposit, a named lease fee, or a mention in the lease text; anything else is unexpected. Flags amount mismatches. |
Deposit | Compares the deposit charged in the ledger to the lease deposit. |
Late fees | Compares late fees charged to the lease late-fee amount and grace period. |
Notes:
National Apartment Association (NAA) leases — the most common US apartment lease — are handled by a dedicated extractor. Signed NAA packets are flattened forms whose fill-in values are detached from their labels in the text, so LedgerMCP reads the reliably-anchored fields (base monthly rent, landlord, tenants, address, effective date) and leaves the positionally ambiguous ones (deposit, late fee) for
get_lease_textrather than guessing.Scanned/image-only PDFs have no text layer; OCR is out of scope and such files are reported with a clear error. Export a text-based PDF if you can.
For unusual leases, lean on
get_lease_textand the confidence levels.
The ledger data model
Entry types and the sign convention
Every source line normalizes to one EntryType, signed by a single rule:
| Meaning | Sign on balance |
| Billed to you |
|
| You paid |
|
| Concession/discount in your favour |
|
| Money returned to you |
|
| Manual correction |
|
A positive ledger balance means money is owed.
Classification (chart of accounts)
The default ClassificationRuleset reflects a renter's perspective: charges →
one expense account per category, concessions → income, payments/refunds
→ a liability (Accounts Payable). Override per category — e.g. treat a
security deposit as an asset:
from ledger_mcp import ClassificationRuleset, AccountType
ruleset = ClassificationRuleset(category_overrides={"security deposit": AccountType.ASSET})Architecture
Layers are decoupled so each is independently testable and swappable:
ledger files ─▶ parsers ─▶ RawEntry ─▶ validate + normalize + classify ─▶ Transaction ─┐
▼
lease PDF ─────▶ lease.pdf ─▶ lease.extract ─▶ LeaseTerms ─────────────────▶ SQLite (SQLAlchemy)
│
┌────────────────────────────────────────────────────────────── ┘
▼
analytics + lease.review (cross-examination)
│
▼
LedgerService ← shared by the MCP server & CLIModule | Responsibility |
| Read ledger formats → |
| Canonical Pydantic models, enums, sign convention. |
| Map entries onto a chart of accounts and apply signs. |
| Semantic checks + reconciliation against reported balances. |
| The parse → validate → normalize → persist pipeline. |
| SQLAlchemy schema, engine, repositories. |
| PDF text extraction, heuristic term extraction, storage, and cross-examination. |
| Deterministic Decimal queries and statements. |
| Application facade owning session lifecycle. |
| FastMCP server exposing the tools. |
Adding a ledger parser
from pathlib import Path
from ledger_mcp.models import RawEntry
from ledger_mcp.parsers import ParseResult, default_registry
class QifParser:
name = "qif"
def can_parse(self, path: Path) -> bool:
return path.suffix.lower() == ".qif"
def parse(self, path: Path) -> ParseResult:
entries: list[RawEntry] = ... # read the file
return ParseResult(entries=entries, source_name=path.name, parser=self.name)
default_registry.register(QifParser())Validation, classification, storage, analytics and lease cross-examination are all format-agnostic, so nothing else changes.
Development
uv sync
uv run pytest # tests
uv run ruff check . # lint
uv run ruff format . # format
uv run pyright # type checkCI (.github/workflows/ci.yml) runs lint, type-check and tests on every push and PR.
Configuration
Variable | Meaning | Default |
| SQLite path or SQLAlchemy URL |
|
The CLI --db flag overrides the environment.
Privacy
Everything runs locally. Your ledger and lease live in a local SQLite file; no
data leaves your machine. The bundled examples/ are synthetic.
License
MIT — see LICENSE.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/luke-nielsen/ledger-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server