Skip to main content
Glama
shiborgi

lastro

by shiborgi

Lastro

Self-hosted, MCP-first financial platform for personal, family, property, project and business finances. Each independent financial scope is a Book.

Lastro is built to be operated by an AI agent over MCP as its primary interface, with a compact web app for review and direct edits. PostgreSQL is the source of truth.

The one idea worth knowing

Lastro separates economic facts from cash movements:

Expense  ≠  Payment
Revenue  ≠  Receipt

An expense is split into settlements — its installments. A payment groups one or more settlements, may cover installments from several different expenses, and has its own due date. A payment's amount is derived from the active settlements it groups; it is never entered directly. The same holds for revenues and receipts.

That distinction is why a general "transactions" table appears nowhere in the schema.

Related MCP server: Agent Ledger

Quick start

Requires Bun 1.4+ and Docker.

cp .env.example .env
# set BETTER_AUTH_SECRET — openssl rand -base64 32

docker compose -f docker-compose.release.yml up -d --build
bun install
bun run bootstrap --email you@example.com --password 'a-long-passphrase'

That prints an MCP credential once. Then:

macOS on Apple Silicon can use Apple Container instead of Docker; see docs/install.md.

Connecting an agent

Lastro speaks MCP over Streamable HTTP and stdio. With the credential printed by bun run bootstrap:

{
  "mcpServers": {
    "lastro": {
      "url": "http://127.0.0.1:3002/mcp",
      "headers": { "Authorization": "Bearer <credentialId>.<secret>" }
    }
  }
}

A credential is bound to one Book. Calls name the Book they act on, and a credential issued for a different one is rejected rather than returning an empty result — so an agent cannot reach another agent's ledger even by asking for it. Give each agent its own Book and credential and they stay separated by construction:

bun run bootstrap --email you@example.com --password '...' --book Butler    --agent agent:butler
bun run bootstrap --email you@example.com --password '...' --book Bartender --agent agent:bartender

Send x-book-id as well to assert which Book you expect: a token for a different one is then rejected instead of writing to the wrong ledger.

The agent gets the whole product across 66 tools: catalog CRUD (institutions, accounts, parties, events, categories, card billing windows, statement descriptors), the expense and revenue cycles, transfers, positions and cash flow, per-event profit attribution, and bank statement import with its review and promotion steps. Every write takes a mandatory idempotency key, every destructive tool requires an explicit confirmation: "confirm", and every mutation appends an audit event naming the agent principal and the operator it acts for.

Bank statements

Lastro reads a statement as a plugin per format — C6 card and account, Nubank account — chosen by the storage path <institution>/<kind>/<file>. A plugin only parses. It never decides that a line is an expense, never invents an institution, and never guesses which merchant a line belongs to: it records the wordings it has never seen, without a destination, for a person to map.

Importing the same file twice is a no-op. Every row carries sha256(institution, account, date, descriptor, amount) plus an occurrence number, so a re-import collides while two genuinely identical purchases on one day stay two rows.

Promotion — turning a reviewed row into an expense, a revenue or a transfer — is a separate step with its own permission, and it refuses rather than guesses when a descriptor names no party or no category. Naming another account on a descriptor is the declaration that its lines are transfers, which is why a transfer seen on both statements collapses to one record instead of two.

Architecture

apps/web       Next.js operator interface (Tailwind CSS v4 + shadcn/ui)
apps/api       authenticated HTTP/JSON API, hosts Better Auth
apps/mcp       MCP adapter — stdio and Streamable HTTP

packages/domain        entities and invariants, no framework
packages/application   commands, queries, authorization, ports
packages/db            PostgreSQL schema, migrations, port implementations
packages/contracts     Zod schemas shared by API, MCP and the web app
packages/auth          agent credentials, RBAC, Book membership
packages/config        validated environment configuration
packages/testing       PostgreSQL-backed integration suite

API, MCP and the web app call the same application handlers. Domain rules never live in routes, tools or components. Only packages/db knows SQL.

Persistence is reached through a port split by aggregate (packages/application/src/ports) whose every member is required, so a repository that cannot answer one fails to compile rather than throwing on the first call that needs it.

Details in docs/architecture.md.

Invariants

  • Money is a bigint amount plus an explicit currency; JSON carries amounts as validated decimal strings. No floats, anywhere.

  • Every command and query is scoped to an authenticated bookId.

  • A settlement may only connect records from the same Book and currency.

  • Active settlements may never exceed their expense's total, nor their receipt's. Both caps are database triggers, because a rule the application alone enforces is a rule two concurrent callers can get around.

  • Status is derived from active settlements, never set directly.

  • Confirmed settlements are voided and replaced, never edited in place.

  • Every mutation is idempotent and appends an AuditEvent.

Development

docker compose up -d          # PostgreSQL 16 on :5432
bun install
bun run db:init               # apply the schema
bun run check                 # typecheck, lint, unit tests, builds
bun run test:integration      # real PostgreSQL, api + mcp, end to end

Changing the schema: edit the right module under packages/db/src/schema/, then run cd packages/db && bun run db:regenerate. That rebuilds the single drizzle/0000_init.sql and appends triggers.sql. There is one migration by design — Lastro ships a database it creates, not one it upgrades.

Contributing

See CONTRIBUTING.md. Security reports go to SECURITY.md.

License

MIT — see LICENSE. UI primitives are copied from shadcn/ui, also MIT; see THIRD_PARTY_NOTICES.md.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    A Model Context Protocol (MCP) server that keeps the books for your personal and business finances using double-entry accounting — driven entirely from an LLM.
    103 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Double-entry accounting ledger MCP server for autonomous agents that enables creating accounts, posting journal entries, and generating financial reports.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for double-entry bookkeeping with idempotent writes, immutable entries, and audit trail. Enables trustworthy financial recording and verification through natural language.
    18 npm
    MIT