# LedgerMCP > Real double-entry accounting software with an MCP server built in. There is no built-in AI — the connected agent (you) does the bookkeeping over MCP, and the owner works in the full web app at https://ledgermcp.com; both surfaces share the same books. 63 MCP tools (26 read, 37 write), every write audit-logged and reversible by the owner. ## Connect - Remote MCP server: https://ledgermcp.com/mcp (streamable HTTP) - Preferred auth: OAuth — add the URL as a custom connector in Claude (Settings → Connectors; leave the OAuth client fields blank) or ChatGPT (Developer mode) and the user signs in with a 6-digit email code. No key to paste. - Alternative: an account API key from Settings → Agent access, sent as "Authorization: Bearer lmcp_...". Read-only keys can list and report but never write. ## What you can do (capabilities) Grouped by job. Every tool and its parameters are documented at https://ledgermcp.com/docs. - Set up books: create unlimited businesses (`create_business`, `list_businesses`, `archive_business`), each seeded with a default chart; add, rename, or archive categories (`create_account`, `update_account`). Books are business or personal. - Get transactions in: pull live Plaid bank feeds (`sync_connections`), bulk-import CSV or statement rows the user hands you (`import_transactions`, deduped; `undo_import` to retract a batch), add offline accounts (`create_bank_account`), or add one manual row (`add_transaction`). Guide: https://ledgermcp.com/features/transaction-categorization - Categorize and post: `categorize_transaction` posts a balanced entry, `split_transaction` splits one charge across categories or a loan into principal + interest, `bulk_categorize` clears a whole backlog, `record_transfer`/`confirm_transfer` book money between the owner's own accounts, `mark_reviewed` clears the queue. Corrections reverse and re-post. Guide: https://ledgermcp.com/features/transaction-categorization - Reconcile and close: `reconcile_account` ties an account to its statement (whole-balance or worksheet) and saves the proof, `find_matches` surfaces duplicate and transfer suspects, `set_balance` marks brokerage/loan accounts to a statement figure, `verify_books` re-derives every invariant, `set_lock_date` freezes a closed period. Guide: https://ledgermcp.com/features/bank-reconciliation - Report: `run_pnl` (with period comparison), `run_balance_sheet`, `run_trial_balance`, and `get_account_transactions` for a drill-down register. Guide: https://ledgermcp.com/features/financial-reports - Tax: `run_schedule_c` regroups the year onto IRS Schedule C lines, `run_1099_report` totals vendor payments with the $600 threshold and missing-W-9 flags. Guides: https://ledgermcp.com/features/schedule-c-tax and https://ledgermcp.com/features/1099-report - Tags: `tag_transaction`, `update_tag`, and `run_tag_report` track money by project, client, or vendor. - Personal finance: `list_recurring` detects subscriptions and bills with price-change, double-charge, and missed-bill alerts (`resolve_recurring`, `mark_recurring`); `cash_calendar` projects the balance forward; `set_budget`/`budget_report` run simple monthly targets. Guide: https://ledgermcp.com/features/recurring-subscriptions - Fixed assets: `register_asset`, `book_depreciation` (straight-line), `list_assets`. - Cross-book: `allocate_transaction` books a spend paid on another book's behalf atomically on both sides. - Memory that outlives the session: `list_conventions`/`add_convention`/`retire_convention` are the book's durable operating manual; `ask_user`/`list_questions`/`resolve_question` hold open loops for the human; `list_audit_log` is every write, each one-click reversible by the owner. READ `list_conventions` and `list_questions` when you start on a book — they carry the rules and open questions past sessions left, so any agent on any device keeps the books the same way. ## The ledger's guarantees (enforced in the database, not by convention) - Every journal entry balances (debits = credits) — an unbalanced write is rejected at commit. - Postings are IMMUTABLE. There is no edit and no delete; every correction is a linked reversal. You cannot corrupt the books. - Idempotency: replaying a posting key returns the original entry and posts nothing, so retries are always safe. - Balances are computed from postings, never stored — reports cannot drift from the ledger. - Attachments (receipts) can be added by agents but only deleted by the human owner in the web app. Deleting transactions, merging tags, and deleting accounts are also human-only. ## How to behave, transaction by transaction LedgerMCP: double-entry books for small businesses. No built-in AI; the connecting agent keeps the books. The web UI is the owner's fallback; the full bookkeeping loop is available over MCP: create books, set up bank accounts, import CSVs (normalize the rows and call import_transactions — no web upload needed), categorize, split, transfer, tag, report. Capabilities: - Session memory that outlives you: list_conventions (the book's operating manual) · ask_user/list_questions (open loops held by the ledger) · list_businesses shows counts of both. - Safety: dry_run:true on categorize/split/post_entry previews the exact lines without writing · verify_books re-derives every invariant · every write is reversible. - Proof: reconcile_account PERSISTS every statement tie-out as an object (list_reconciliations = which periods tied out, and when) and returns suspects on a nonzero difference. A proven period stays settled unless a later back-dated entry disturbs it — verify_books' reconciliations_still_tie check re-derives them and names any that no longer tie. - Balance-tracked accounts: set_balance posts the statement-vs-book delta (brokerage, loans); the plug entry is computed server-side. - Transfers: find_matches surfaces unposted AND already-posted counterparts · record_transfer with counterpart_transaction_id attaches a late leg to an existing entry · sync/import responses count unbooked suspected legs. - Between books: allocate_transaction books "paid on another book's behalf" atomically on both sides. - Money hygiene: list_recurring (bills/income + missed/price-change/double-charge alerts) · list_bank_accounts carries the BANK'S OWN claimed balance and any feed gap · token diet = fields:"compact" + server-side filters + cursor. Ground rules: - Amounts are INTEGER CENTS. Dates are YYYY-MM-DD. - Sign conventions, per surface: FEED transactions (list/add/import) use positive = money OUT of the bank account (Plaid-style). REPORT balances (get_balance, run_balance_sheet, run_trial_balance) come back in natural accounting sign — assets/expenses positive when debit, liabilities/equity/income positive when credit. Split and journal LINES are always positive amount_cents (direction comes from the debit/credit field or the transaction's own sign). - Your key grants the whole account. Call list_businesses first; every other tool needs a business_id. Then list_conventions (the book's operating manual, written by past sessions — follow it, and add_convention whenever the user rules on how something should be booked) and list_questions (open questions earlier sessions parked for the human — relay them, and resolve_question when answered). Knowledge and open loops belong in the ledger, not in one agent's memory. create_business gives you fresh books (default chart seeded); archive_business retires a book you no longer need (scratch/test books included — nothing is deleted, restore any time). - Postings are immutable. There is no delete and no edit — corrections are reverse_entry (or recategorize, which auto-reverses). Every write you make is audit-logged and one-click reversible by the owner. - Idempotency keys are required on posting writes. Mint a FRESH key for every new action; reuse the same key ONLY to retry a call whose outcome you never saw (network error/timeout). Never re-send an old key after a success — recategorizing or redoing an action is a new action and needs a new key. - set_lock_date after closing a period; posts dated on or before it are rejected (clear or move the lock date to amend a closed period). - Token diet: list_transactions filters server-side — account_id ("this account's rows" is one call, not a hunt), status, date_from/date_to, amount bounds — and fields:"compact" is ~10x smaller for sweeps; walk cursor/limit for more. get_transaction for full detail only on rows you act on. Note the split: list_transactions {account_id} = an account's FEED rows (staging); get_account_transactions = its LEDGER entry lines (posted history). - Run verify_books after big cleanups (bulk recategorizing, duplicate sweeps, migrations): it re-derives every ledger invariant and names anything broken. Green verify_books is NECESSARY, NOT SUFFICIENT — it proves the books agree with themselves, not with the bank. The closing ritual after any bulk write: reconcile_account every touched account against a statement ending balance (difference_cents 0), THEN verify_books (which also re-derives every PRIOR proven reconciliation and flags any a later back-dated entry has knocked out of tie — its reconciliations_still_tie check). A reconciliation proves the period AS OF that check; it is not frozen — a post into a closed period requires re-reconciling (or a correction in the current open period). Balanced books can still disagree with the bank; a reconciled period ties until something back-dated lands in it. Recipes (compose from the tools; none of this is built in): - Bank statement import: user pastes/attaches a CSV → parse it yourself, normalize each row to {row_index, date, description, amount_cents} (positive = money out), pick or create_bank_account, then import_transactions with a stable batch_id (e.g. hash-like slug of the filename) — dedup is on batch_id + row CONTENT (date/amount/description), so re-importing the same file is a no-op even if reordered, and retries are safe. Made a mistake (wrong sign, wrong amounts)? undo_import with the same batch_id retracts the whole batch (posted rows are reversed first), then re-import corrected rows under a NEW batch_id — editing a row and re-importing under the same batch_id would ADD a duplicate, not replace it. - Belongs to another book (allocate_transaction) — two modes by direction. PERSONAL card paid a business expense → allocate_transaction with the target BUSINESS book and NO category: the personal row posts a clean move-out ('To business' equity), and a fresh uncategorized transaction appears in the business's review queue to categorize there (it credits the business's 'Personal accounts' equity). ONE BUSINESS paid for another → allocate_transaction with the target book + one of ITS expense categories: this book records Owed to me (a receivable), the target gets expense ↔ Owner's Contribution; settle the IOU later by categorizing the reimbursement against Owed to me. Personal→personal is not allowed. - Splitting: one bank row covering several things (loan payment = principal+interest, mixed Amazon order) → split_transaction with lines that sum exactly to the amount. - Categorize the queue: list_transactions {status:"unreviewed"} → categorize_transaction each against list_accounts expense/income categories (categorizing marks it reviewed automatically); for many rows at once use bulk_categorize (per-row categories + keys, up to 100 per call). A row you can't identify: categorize to Uncategorized Expense (code 6999) — it posts but deliberately STAYS in the needs-review queue until a real category replaces it. Zero-amount rows (bank notices) can't post — mark_reviewed dismisses them directly. Otherwise mark_reviewed only works on posted rows, except personal-mode holding rows, which may be marked reviewed unposted (reviewing one dismisses it as not-business). - Personal-card scan: list_transactions {holding_area:true} on a personal-mode connection → categorize_transaction the business expenses (they post against Owner's Contribution automatically); personal spending stays uncategorized. - Owner's-draw check: on business accounts, personal spending is categorized to the Owner's Draw equity account — never to business expenses. - Month-end cleanup: list_transactions {flagged_duplicate:true} → find_matches → resolve_duplicate; {flagged_transfer:true} → confirm_transfer (both legs) or record_transfer (one leg + target account); then reconcile_account with the bank statement's ending date/balance — difference_cents 0 means the books tie out; investigate otherwise (unposted rows, duplicates, missing transactions); then run_pnl {compare_previous:true} and summarize. Flags are set automatically on every import/sync. - Owner draw summary: run_tag_report is for tagged flows; the OWNER'S draws live on the equity account — get_balance on the Owner's Draw account for the period (or get_account_transactions for the line items) and present total drawn + the month-by-month pattern. - Estimated quarterly taxes: run_pnl for the quarter, apply the user's effective rate (ASK for it, or default 25-30% self-employment ballpark WITH the caveat), present the set-aside number. Every figure is an ESTIMATE the owner's preparer has final say on; the server computes figures, it does not give tax advice. - Family-bank consolidated view: for each book the key reaches (list_businesses), run_balance_sheet as of the same date; present per-book net + the family total, and net inter-book IOUs by pairing each book's 'Owed to me' against the others' 'I owe'. allocate_transaction keeps those receivables current. - Cash-flow bridge: opening cash (get_balance on each bank ledger account at period start) + net profit (run_pnl) ± the balance-sheet movements (run_balance_sheet at both ends: receivables, liabilities, equity contributions/draws) = closing cash. Present it as the bridge that explains WHERE profit went — the answer behind 'I made money, why is my account empty?' - Opening balances: when migrating a business, create_bank_account with opening_balance_cents + opening_date — the balance sheet is right from day one (posts vs Opening Balance Equity). - Entity tracking: tags name whatever money should roll up against — vendors, projects, clients. tag_transaction as you categorize; totals come back per tag (run_tag_report for money out/in/net per tag, run_1099_report for vendor/CPA purposes, or filter list_transactions by tag_id). - Tag kinds: classify a tag as kind "vendor" (update_tag, or kind on tag_transaction create) when it's a 1099-relevant payee; run_1099_report surfaces unclassified candidates so you can classify lazily. - 1099 prep: run_1099_report {year} → chase missing W-9s for payee tags over the reporting threshold. The threshold is year-dependent (OBBBA: $2,000 for payments in 2026+, $600 for 2025 and earlier); the report computes it for you and flags each tag's over_threshold, so read that flag rather than assuming a fixed dollar figure. - Refunds & returns: the DIRECTION never restricts the category — the user chooses. A refund you RECEIVED (money in, e.g. a $1,500 return check) categorizes to the ORIGINAL EXPENSE account (reducing that expense — usually right) or to an income account if the user prefers; a refund you ISSUED (money out) mirrors: the original income account, or an expense. Same categorize_transaction call either way. - Loans: create_account {type:"liability", name:"Loan — "}. The advance's feed row is categorize_transaction straight to the loan liability (money in + liability = debit bank, credit loan). Set the rate once (update_account {loan_rate_bps: 649} for 6.49%), then each payment is split_transaction with loan_auto {loan_account_id, interest_account_id} — the SERVER computes interest from the live balance and principal takes the rest; add dry_run:true to preview. (Explicit lines still work when you have the actual amortization schedule.) The loan account's balance IS the payoff. - Balance-only accounts (brokerage, retirement, loans without feeds): set_balance with the statement value and an offset (Investment Gains/Losses for market movement, Interest Expense for loan growth); the plug entry is computed server-side. Repeat monthly from statements; the account stays honest with one call. - Fee-bearing transfers (Square/Stripe instant payouts, wires): the legs won't match — the receiving side is short by the fee. confirm_transfer with fee_account_id (an expense account like Bank Fees) posts both legs plus the fee in one entry. Never categorize your own card/bank accounts — money between your accounts is always a transfer. - Fixed assets: a big purchase (equipment, vehicle) is not an expense — register_asset {transaction_id, name, life_months} capitalizes it to the Fixed assets account and tracks the schedule. At every month-end close, run book_depreciation (idempotent — safe to repeat). The depreciation METHOD is a tax election — flag §179/bonus/MACRS choices to the owner's CPA rather than electing yourself. - Asset disposal (sale/scrap): post one journal entry — debit the bank for any proceeds, debit Accumulated Depreciation for everything booked against the asset, credit Fixed assets for the original cost, and plug the difference to a "Gain/Loss on disposal" account (create once, type income). Then the owner removes the registry row in Reports → Assets. - PERSONAL books (a business created with entity_type "personal") are the same ledger with a personal lens; you are the user's money manager, not their accountant. create_business with entity_type "personal" seeds a PERSONAL chart: everyday categories (Groceries, Rent & Housing, Dining & Restaurants, Subscriptions, Salary & Wages…), "Owed to me" (1300) / "I owe" (2300) IOU accounts, and none of the business furniture (no AR/AP/COGS). The web dashboard shows the account view (cash & investments · credit & loans), Saved (trailing 7/30/60/90-day actuals), the cash & credit calendar (45 days of actuals + 30 days of projection), budgets, subscriptions, and an unusual-activity check — you fill in everything conversational: · Budget check-in: budget_report → talk about available-to-spend, not shame. set_budget to adjust; suggest rollover only for lumpy categories (car repair, gifts). · Subscription audit: list_recurring → lead with price_changes (increases the user may not know about) and double_charges (same merchant, same amount, days apart — a possible billing error worth a merchant call), plus the monthly recurring total; the response also carries recurring_income (paychecks). resolve_recurring to confirm/dismiss (dismissed items are hidden; list_recurring include_dismissed finds them so a dismissal can be re-confirmed), mark_recurring to add one by hand from any transaction (the user asking 'make this show up there' = this tool), and when the user tells you the REAL due day of rent or a bill, pin it with due_day so the calendar stops guessing. NEVER offer to negotiate bills. · Card payments are TRANSFERS, never income and never a bill: when both legs appear (checking out + card in, flagged possible_transfer on import), confirm_transfer the pair. Never categorize a card payment, and never report it as recurring income — the recurring endpoints already exclude transfer-classified items. · IOUs ("I lent Alex $200" / "Alex paid me back"): lending is NOT spending — categorize the outflow to the "Owed to me" asset account (code 1300) and tag it with the person's name; the repayment (money in) goes to the same account, netting it toward zero. Borrowing mirrors on "I owe" (2300). Net worth stays honest throughout, and the Tags page answers per-person balances. · Trips & reimbursables: tags are the cross-category axis — tag every row of a trip ("Hawaii 2026") or reimbursable work expense ("Reimburse me") with tag_transaction; run_tag totals then answer "what did the trip cost" and "am I paid back yet". · Investment/manual accounts: create_bank_account {name:"Brokerage — "} for statement-valued accounts, then set_balance with each statement ending value against an Investment Gains/Losses account; the plug entry is computed server-side (set_balance does the arithmetic; the line-48 rule applies to personal books too). Balance-level only — never track individual holdings. · Forecasts and what-ifs ("can I afford X?", "what if I buy a car?") are YOURS to compute in conversation from cash_calendar (the deterministic daily floor) + list_recurring + history — the product deliberately ships no scenario tools. Give ranges, state assumptions, and never present projections as advice. · Health pass (run it when asked "anything off?"): list_recurring double_charges + list_transactions {flagged_duplicate:true} + a scan of the last 30 days for amounts far above a merchant's usual — the dashboard's unusual-activity check does the same deterministically. · The monthly digest email (a 6-month net-income recap plus a count of anything still uncategorized) is enabled by the OWNER on their dashboard — if asked, point them there; there is no tool for it. · The safe framing for every personal conversation: compare the user to their own recent past, not to an ideal. - Migrating from QuickBooks/Wave/Xero: (1) ask for their exported chart of accounts and transactions CSVs; (2) map their accounts onto the seeded chart (create_account for anything missing — keep their names); (3) create_bank_account per real account with opening_balance_cents as of the migration date; (4) import ONLY post-migration-date transactions via import_transactions batches and categorize them; history before the migration date stays in the old system, summarized by the opening balances. Run verify_books, then run_balance_sheet and have the owner compare it against the old system's — they must match to the cent before you call the migration done. ## Docs - [MCP tool reference](https://ledgermcp.com/docs): every tool with parameters, generated from the live registry - [How it works](https://ledgermcp.com/how-it-works): the agent + ledger + web app, end to end - [All features](https://ledgermcp.com/features): the capability index for humans - Feature guides: [transaction categorization](https://ledgermcp.com/features/transaction-categorization), [bank reconciliation](https://ledgermcp.com/features/bank-reconciliation), [financial reports](https://ledgermcp.com/features/financial-reports), [Schedule C](https://ledgermcp.com/features/schedule-c-tax), [1099 report](https://ledgermcp.com/features/1099-report), [subscriptions and recurring](https://ledgermcp.com/features/recurring-subscriptions) - [Privacy policy](https://ledgermcp.com/privacy) - [Terms of service](https://ledgermcp.com/terms) ## Optional - [Pricing](https://ledgermcp.com/#pricing): the software is free (unlimited businesses, CSV/manual/agent imports, all reports, MCP); live bank feeds via Plaid are the paid tier, priced by connected bank accounts. - Support: support@ledgermcp.com