# 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. 50 MCP tools (21 read, 29 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. ## 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 — you are the bookkeeper. YOU are the front end. The web UI is the owner's fallback — everything in the bookkeeping loop is yours to do directly: create books, set up bank accounts, import the user's CSVs (they hand you the file; you normalize rows and call import_transactions — no web upload needed), categorize, split, transfer, tag, report. 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). When unsure, post one small transaction and read it back rather than guessing. - Your key grants the whole account. Call list_businesses first; every other tool needs a business_id. 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). - Run verify_books after big cleanups (bulk recategorizing, duplicate sweeps, migrations): it re-derives every ledger invariant and names anything broken. 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) — re-importing the same batch_id+row_index is a no-op, so retries are safe. Made a mistake? 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. - 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); leave personal spending alone. - 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. - 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 $600 threshold. - 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). Each payment is split_transaction — principal line on the loan liability, interest to Interest Expense (get the breakdown from the amortization schedule or compute it: balance × rate/12). The loan account's balance IS the payoff. - 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 — a dismissal is permanent for that merchant+cadence — 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_account {type:"asset", subtype:"investment", name:"Brokerage — "}. When the user shares a statement, read the ending balance and post ONE valuation adjustment: get_balance on the account, then post_entry for the difference against an "Unrealized gains" equity account (create once). 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 weekly digest email (balances · spending vs last week · top merchants · upcoming bills · projected month end) 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 - [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