{"slug": "openledger-local-first-double-entry-accounting-mcp-native-apache-2-0", "title": "OpenLedger – Local-first double-entry accounting, MCP-native (Apache 2.0)", "summary": "OpenLedger, an Apache 2.0-licensed local-first double-entry accounting system with an SQLite backend and a Model Context Protocol (MCP) server exposing 15 tools, has been released by Attri Inc. The ledger enforces immutable transactions, contra-posting corrections, and an append-only audit log, and is designed to be queryable by both humans and AI agents via natural language. The project is part of a family of local-first agent services including open-crm and openwatch.", "body_md": "**Local-first double-entry accounting, queryable by humans and agents.**\n\nSQLite-backed ledger. MCP server with 15 tools. Immutable transactions, contra-posting corrections, append-only audit log.\n\nPart of a family of local-first agent services: open-crm (memory) · openwatch (observability) · **openledger (money)**.\n\n```\ncd open-ledger\npython3 -m venv .venv && source .venv/bin/activate\npip install -r requirements.txt\npython scripts/seed.py            # bootstrap a fresh dev DB with sample books\npython run_mcp.py                 # stdio transport (default)\n```\n\nFor Claude Code:\n\n```\nclaude mcp add openledger -s user -- \\\n  /absolute/path/to/open-ledger/.venv/bin/python \\\n  /absolute/path/to/open-ledger/run_mcp.py\n```\n\nSee [docs/claude-connector.md](/Attri-Inc/open-ledger/blob/main/docs/claude-connector.md) for the full setup.\n\n```\n                ┌─────────────────────────────────────┐\n                │          SQLite ledger              │\n                │  accounts · transactions ·          │\n                │  entry_lines · audit_log · settings │\n                └──────────────────┬──────────────────┘\n                                   │\n                                   ▼\n                         MCP server (stdio / SSE :8791)\n                         15 tools — reads + safe writes\n                                   │\n                                   ▼\n                         Claude Desktop, Claude Code,\n                         agent frameworks\n```\n\nA layered architecture (SOLID): the transport, business rules, and persistence\nare separated, and each depends only on the layer's abstraction — not its\nimplementation. Swapping SQLite for Postgres later touches only `repositories/`\nand `container.py`.\n\n```\nsrc/\n├── domain/          pure constants + typed error hierarchy (no I/O)\n├── infrastructure/  Database connection, Unit of Work, id/clock helpers\n├── repositories/    protocols.py  — narrow Reader/Writer contracts\n│                    sqlite.py     — the only code that writes SQL (aiosqlite)\n├── services/        accounts · ledger · reports (Strategy) · audit · query\n│                    — the only layer with business rules / invariants\n├── serialization.py response/error envelope helpers\n├── container.py     composition root — wires SQLite repos into services\n└── mcp_server.py    thin MCP transport adapter over the services\nrun_mcp.py           stdio entry point for Claude Desktop / Code\nscripts/             seed.py (sample data) · schema.sql · smoke_test.py\ntests/               service-level tests of the core invariants\n```\n\nQuerying is **raw parameterized SQL over `aiosqlite`** (no ORM); all SQL lives\nbehind the repository protocols, so services never see a query.\n\n1. Every transaction has ≥ 2 entry lines and `sum(debits) == sum(credits)` — enforced\nin the write path inside one DB transaction.\n2. All amounts are **integer minor units (cents)** . No floats anywhere.\n3. Transactions and entry lines are **immutable** . Corrections happen via`reverse_transaction` (contra posting), never UPDATE/DELETE.\n4. Every mutation writes an audit-log row **in the same DB transaction** .\n\n| Group | Tools | \n|---|---|\n| Accounts | `list_accounts` ·`get_account` ·`get_balance` ·`get_account_ledger` ·`create_account` | \n| Journal | `get_transaction` ·`search_transactions` ·`post_transaction` ·`transfer_funds` ·`reverse_transaction` | \n| Reports | `get_trial_balance` ·`get_profit_loss` ·`get_balance_sheet` | \n| Audit | `get_audit_log` | \n| Escape hatch | `run_query` (SQL`SELECT` only) | \n\n| Env var | Default | Notes | \n|---|---|---|\n| `OPENLEDGER_DB` | `./data/openledger.db` | SQLite path | \n| `MCP_TRANSPORT` | `stdio` (via`run_mcp.py` ) | `stdio` or`sse` | \n| `MCP_PORT` | `8791` | SSE only | \n\n- \"How much cash do we have right now?\"\n- \"Show me the P&L for January.\"\n- \"Are the books balanced?\" (trial balance)\n- \"Post a $250 cash sale for today.\"\n- \"Move $500 from Wallet A to Wallet B.\"\n- \"What was reversed recently, and why?\"\n\nContributions welcome — see [CONTRIBUTING.md](/Attri-Inc/open-ledger/blob/main/CONTRIBUTING.md) and our\n[Code of Conduct](/Attri-Inc/open-ledger/blob/main/CODE_OF_CONDUCT.md). Report vulnerabilities per [SECURITY.md](/Attri-Inc/open-ledger/blob/main/SECURITY.md).\n\nLicensed under the **Apache License 2.0** — see [LICENSE](/Attri-Inc/open-ledger/blob/main/LICENSE).", "url": "https://wpnews.pro/news/openledger-local-first-double-entry-accounting-mcp-native-apache-2-0", "canonical_source": "https://github.com/Attri-Inc/open-ledger", "published_at": "2026-09-07 02:47:58+00:00", "updated_at": "2026-09-07 02:59:34.223913+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["Attri Inc.", "OpenLedger", "SQLite", "MCP", "Claude Code", "Claude Desktop"], "alternates": {"html": "https://wpnews.pro/news/openledger-local-first-double-entry-accounting-mcp-native-apache-2-0", "markdown": "https://wpnews.pro/news/openledger-local-first-double-entry-accounting-mcp-native-apache-2-0.md", "text": "https://wpnews.pro/news/openledger-local-first-double-entry-accounting-mcp-native-apache-2-0.txt", "jsonld": "https://wpnews.pro/news/openledger-local-first-double-entry-accounting-mcp-native-apache-2-0.jsonld"}}