Skip to main content
Glama
Septillioner

ezbookkeeping-mcp

by Septillioner

ezbookkeeping-mcp

Let AI assistants and scripts read and manage your self-hosted ezBookkeeping books, safely.

CI License: MIT Node.js >= 18 MCP

An MCP server and a command-line tool for ezBookkeeping v2.1.0. Both are built on one core, so Claude, Cursor, a custom agent or a PowerShell script all get the same tools and the same JSON.

What it does

  • Accounts and transactions: balances, filtered and paginated transaction lists, and add, edit or delete for expenses, income and transfers.

  • Categories and tags: list and create.

  • Analysis:

    • monthly income and expense with a per-category breakdown;

    • category reports;

    • net worth with a debt summary that lists overdraft, credit cards and personal debts separately;

    • cash flow in day, week or month buckets with a running balance;

    • recurring payment and subscription detection;

    • installment plans parsed from notes on debt accounts.

  • Bulk import: preview a file with ezBookkeeping's own parser, then import validated rows.

  • Exact money handling:

    • amounts are returned both as integer minor units (kurus) and as decimal strings (tl);

    • input with a comma or a dot is converted exactly;

    • totals are reported per currency and never summed across currencies.

  • Safe by default:

    • read and write tools are separated, and a read-only switch disables all writes;

    • every write has a dry run;

    • delete requires confirmation;

    • the API token is never printed.

  • Easy to deploy: two self-contained bundles (cli.cjs, server.cjs) that run with plain node. No node_modules are needed, and they work from any directory.

  • Full UTF-8 support: Turkish and other non-ASCII category, tag and account names work everywhere.

Related MCP server: lastro

Tools

Tool

Access

Description

read_server_info

read

ezBookkeeping version and the active configuration (never the token)

read_accounts

read

Accounts with balances, kind (asset/liability), liability type and per-currency totals

read_transactions

read

Transactions filtered by date, account, category, tag, type and keyword; cursor pagination or fetch-all

read_transaction

read

One transaction by id

read_categories

read

Categories with full Parent / Child names

read_tags

read

Tags

read_stats_monthly

read

Income, expense, net and category breakdown for 1–36 months

read_stats_categories

read

Expense or income by sub-category or parent category over a date range

read_networth

read

Assets, liabilities and net worth per currency; debts split into overdraft, credit cards and personal debts

read_cashflow

read

Inflow, outflow and running balance in day, week or month buckets

read_recurring

read

Recurring payments and subscriptions: cadence, average amount, confidence and next expected date

read_installments

read

Installment plans parsed from liability account notes (grammar)

read_import_parse

read

Preview a bank or export file through the server's import parser; saves nothing

write_transaction_add

write

Add an expense, income or transfer

write_transaction_edit

write (destructive)

Change only the given fields of a transaction; keeps pictures and location

write_transaction_delete

write (destructive)

Delete one transaction; requires confirm: true

write_category_create

write

Create a primary category or a sub-category

write_tag_create

write

Create a tag

write_import_transactions

write

Import up to 5,000 validated transactions in one request

The MCP annotations match:

  • Read tools: readOnlyHint: true, destructiveHint: false, idempotentHint: true.

  • Edit and delete: destructiveHint: true, idempotentHint: true.

  • Add, create and import: readOnlyHint: false, idempotentHint: false.

Every write tool accepts dryRun.

Quick start

From source

git clone https://github.com/Septillioner/ezbookkeeping-mcp.git
cd ezbookkeeping-mcp
npm install
npm run build        # dist/cli.cjs and dist/server.cjs
export EZBK_BASE_URL=http://localhost:8090
export EZBK_TOKEN=<your ezBookkeeping API token>
node dist/cli.cjs accounts list

Prebuilt bundles

Download ezbookkeeping-mcp-windows.zip from Releases. It contains cli.cjs and server.cjs and works on any OS with Node.js 18.17 or newer, despite the name. Unzip it anywhere and run node cli.cjs ping. For a step-by-step Windows setup, see docs/DEPLOY_WINDOWS.md.

Create the API token in ezBookkeeping under Settings → API tokens.

Configuration

All settings are environment variables. The tools do not read .env files; .env.example lists them for reference.

Variable

Default

Description

EZBK_BASE_URL

http://localhost:8090

ezBookkeeping URL. A trailing /api or /api/v1 is removed

EZBK_TOKEN

–

API token

EZBK_TOKEN_FILE

–

Path to a file containing the token, used when EZBK_TOKEN is empty

EZBK_TIMEZONE

Europe/Istanbul

IANA timezone for dates, month boundaries and the X-Timezone-Name header

EZBK_READ_ONLY

false

true disables every write tool (MCP and CLI)

EZBK_TIMEOUT_MS

30000

Per-request timeout in milliseconds (100–600000)

EZBK_OVERDRAFT_ACCOUNT_IDS

–

Comma-separated debt account ids to classify as overdraft

EZBK_DEBUG

false

Log requests to stderr. Authorization is always redacted

EZBK_HTTP_HOST

127.0.0.1

Bind address for the optional HTTP transport (server.cjs --http <port>)

The token is resolved in this order: EZBK_TOKEN, then EZBK_TOKEN_FILE, then (Windows only) C:\ezbookkeeping\API_TOKEN.txt. Token files are parsed leniently:

  • a BOM, CRLF line endings, blank lines and # / // / ; comments are allowed;

  • the token can be bare, KEY=value, KEY: value, quoted, or prefixed with Bearer;

  • when several lines qualify, a JWT-looking value wins.

MCP client setup

Cursor

~/.cursor/mcp.json (global) or .cursor/mcp.json (per project):

{
  "mcpServers": {
    "ezbookkeeping": {
      "command": "node",
      "args": ["/opt/ezbookkeeping-mcp/server.cjs"],
      "env": {
        "EZBK_BASE_URL": "http://localhost:8090",
        "EZBK_TOKEN_FILE": "/home/you/.config/ezbookkeeping/API_TOKEN.txt",
        "EZBK_TIMEZONE": "Europe/Istanbul"
      }
    }
  }
}

Claude Desktop

Config file:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

On Windows, use escaped backslashes in paths:

{
  "mcpServers": {
    "ezbookkeeping": {
      "command": "node",
      "args": ["C:\\ezbookkeeping\\mcp\\server.cjs"],
      "env": {
        "EZBK_BASE_URL": "http://localhost:8090",
        "EZBK_TOKEN_FILE": "C:\\ezbookkeeping\\API_TOKEN.txt",
        "EZBK_TIMEZONE": "Europe/Istanbul",
        "EZBK_READ_ONLY": "true"
      }
    }
  }
}

The same blocks work in either client. Set EZBK_READ_ONLY to "true" or add "--read-only" to args for an assistant that should only read; the write tools are then not listed at all.

node server.cjs --http 8765 serves stateless Streamable HTTP at POST http://127.0.0.1:8765/mcp.

CLI

node cli.cjs <resource> <action> [--flags] [--json] [--compact] [--out file.json] [--input file.json|-]
node cli.cjs accounts list --json
node cli.cjs transactions list --from 2026-10-01 --to 2026-10-31 --account 3001 --json
node cli.cjs transactions list --keyword market --all --json
node cli.cjs transactions add --type expense --amount 125.50 --account 3001 --category 2011 \
  --time "2026-10-21 12:30" --comment "Groceries" --dry-run
node cli.cjs transactions add --type transfer --amount "8.000,00" --account 3001 --to-account 3003 --category 4012
node cli.cjs transactions edit --id 123 --amount 99.90
node cli.cjs transactions delete --id 123 --yes
node cli.cjs stats monthly --month 2026-09 --months 3 --json
node cli.cjs stats categories --from 2026-09-01 --to 2026-09-30 --level parent
node cli.cjs networth
node cli.cjs cashflow --from 2026-01-01 --to 2026-10-31 --bucket month --json
node cli.cjs recurring --json
node cli.cjs installments --json
node cli.cjs import parse --type ofx --file statement.ofx --json
node cli.cjs tools list
node cli.cjs help transactions add

Command

Tool

server info (ping), accounts list, categories list, tags list

reference reads

transactions list / get

read_transactions / read_transaction

stats monthly / stats categories

read_stats_monthly / read_stats_categories

networth, cashflow, recurring, installments

analysis reads

import parse

read_import_parse

transactions add / edit / delete

write tools

categories create, tags create, import run

write tools

Flags

  • Flags are the tool input names in kebab case (--destination-account-id).

  • Short aliases: --account, --to-account, --category, --tag(s), --limit, --all, --yes, --q, --parent, --hidden, --type (file type for import parse).

  • Lists accept 3001,3003 or ["3001","3003"].

  • Unknown flags and stray arguments are errors, so a typo never silently drops a filter.

Output and exit codes

  • Without --json, the output is a short human-readable view.

  • Exit codes: 0 success; 1 API, network or configuration error; 2 usage error, validation error or read-only refusal.

Amounts and dates

  • Output: every amount is { "kurus": 12550, "tl": "125.50", "currency": "TRY" }.

    • kurus is the integer the API stores (minor units of that currency).

    • tl is the same value as a decimal string.

    • Totals are lists with one entry per currency.

  • Amount input: 125.50, 125,50, 1.234,56, 1,234.56, ₺125,50, 125.50 TL or a number.

    • More than two decimals is rejected, never rounded.

    • 1,234 and 1.234 are rejected as ambiguous. Write 1234, 1234.00 or 1.234,00.

  • Date input: YYYY-MM-DD (a to date covers the whole day), YYYY-MM-DD HH:mm[:ss] in EZBK_TIMEZONE, ISO 8601 with an offset, or unix seconds.

    • Month boundaries are local midnight.

    • transactions add uses the current time when --time is omitted.

JSON envelope

Every MCP result and every --json output has the same shape:

{
  "ok": true,                 // false on any error
  "data": { },                // present when ok
  "error": {                  // present when !ok
    "kind": "validation | auth | not_found | api | http | network | timeout | read_only | config | parse | internal",
    "message": "human readable; never contains the token",
    "httpStatus": 400,                                   // optional
    "apiErrorCode": 205001,                              // optional, ezBookkeeping error code
    "issues": [{ "path": "amount", "message": "..." }],  // optional, input validation
    "context": { "importedCount": 3, "totalCount": 5 }   // optional, from the server
  },
  "meta": {
    "tool": "read_networth",
    "access": "read",                                    // or "write"
    "readOnlyMode": false,
    "timezone": "Europe/Istanbul",
    "generatedAt": "2026-10-21T10:00:00+03:00",
    "version": "0.1.0"
    // plus tool-specific counters: count, pages, truncated, transactionsScanned, ...
  }
}

Money = { kurus, tl, currency }; Money[] = one entry per currency, sorted by currency code.

Command

data

accounts list

accounts[]: {id, name, parentId, category, categoryCode, kind, liabilityType, currency, balance, hidden, comment, isGroup, subAccountIds?, creditCardLimit?, creditCardStatementDate?}; totals: {assets: Money[], liabilities: Money[]}

transactions list

items: Transaction[], nextCursor (pass back as --cursor), totalCount?

transactions get

transaction: Transaction

Transaction

{id, type, typeCode, time, timeLocal, date, utcOffset, category: {id, name}, sourceAccount: {id, name, currency}, destinationAccount, amount, destinationAmount, tags: [{id, name}], comment, hideAmount, editable}

categories list

categories[]: {id, name, fullName, type, typeCode, parentId, parentName, isPrimary, hidden, comment}

tags list

tags[]: {id, name, groupId, hidden}

stats monthly

months[]: {month, start, end, income, expense, net: Money[], counts, expenseByCategory[], expenseByParentCategory[], incomeByCategory[]}; a row is {categoryId, name, currency, total, count, sharePct}

stats categories

{from, to, fromDate, toDate, type, categories[], totals: Money[]}

networth

{assets, liabilities, totalOwed, netWorth: Money[], debts: {overdraft, creditCards, personalDebts}, warnings[]}; each debt group is {accounts[], totalOwed}; card rows add limit, available, utilizationPct, statementDay

cashflow

{from, to, fromDate, toDate, bucket, scope[], currencies[]: {currency, openingBalance, closingBalance, currentBalance, totalInflow, totalOutflow, totalAdjustments, totalInternalTransfers, net, buckets[]: {key, inflow, outflow, adjustments, net, runningBalance, count}}}

recurring

{from, to, items[]: {label, matchedBy, type, category, accounts[], currency, cadence, medianIntervalDays, occurrences, averageAmount, minAmount, maxAmount, firstDate, lastDate, nextExpectedDate, overdue, active, confidence, transactionIds[]}, estimatedMonthlyCost: Money[]}

installments

{today, accounts[]: {account, parsed, plans[], unparsed[], raw}, totalRemaining: Money[]}; a plan has counts, amounts, dates, nextDueDate, schedule[] and warnings[]

import parse

{totalCount, items[]: {index, type, time, timeLocal, categoryId, sourceAccountId, destinationAccountId, amount, destinationAmount, tagIds, comment, unresolved[], original*}}

transactions add / edit

{transaction}; with --dry-run: {dryRun: true, request: {method, url, headers, body}}

transactions delete

{deleted: true, transaction} or the dry-run request

categories create / tags create

{category} / {tag} or the dry-run request

import run

{result: {finished, importedCount, totalCount}, count} or the dry-run request

tools list

tools[]: {name, command, access, title, annotations}

How bots and agents use it

MCP clients (Claude, Cursor, agent frameworks):

  • Connect to server.cjs and call the tools.

  • Results come back as the JSON envelope, both as text and as structuredContent.

  • Failed calls set isError.

Scripts and non-MCP agents call the CLI with an absolute path and --json. One install can be shared by several bots, because nothing is read from the current directory.

# Windows PowerShell 5.1: make the console UTF-8 so non-ASCII names print correctly
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

$r = node C:\ezbookkeeping\mcp\cli.cjs networth --json | ConvertFrom-Json
if (-not $r.ok) { throw "$($r.error.kind): $($r.error.message)" }
$r.data.debts.creditCards.totalOwed | ForEach-Object { "$($_.tl) $($_.currency)" }

# Large results: write the envelope to a UTF-8 file instead of parsing console output
node C:\ezbookkeeping\mcp\cli.cjs transactions list --from 2026-01-01 --all --compact --json --out tx.json > $null
$tx = Get-Content tx.json -Raw -Encoding UTF8 | ConvertFrom-Json

# Complex input as JSON on stdin avoids quoting problems
'{"type":"expense","amount":"349,90","accountId":"3004","categoryId":"2023","comment":"Streaming"}' |
  node C:\ezbookkeeping\mcp\cli.cjs transactions add --input - --dry-run --json

Recommendations for agent builders:

  • Give read-only agents EZBK_READ_ONLY=true.

  • Always run a write once with dryRun and show it to the user before sending it for real.

  • Branch on error.kind, not on message text.

  • Use nextCursor (or fetchAll with maxItems) for long transaction lists.

Safety model

Guard

Behaviour

Read-only mode

EZBK_READ_ONLY=true or --read-only: write tools are not registered in MCP, and the CLI and core refuse them (read_only, exit code 2)

Read/write separation

read_* and write_* prefixes, MCP annotations, and separate READ and WRITE sections in CLI help. Read tools only send GET requests (except the import preview, which saves nothing)

Dry run

Every write accepts dryRun / --dry-run and returns the exact request with Authorization redacted. Nothing is sent

Confirm on delete

write_transaction_delete requires confirm: true (--yes) and returns the deleted transaction

Validation before writes

Accounts, sub-categories of the right type, hidden categories, tags, transfer direction, currencies and amount format are checked before any request. Ids are passed through as strings (int64-safe). Unknown input keys are rejected

Edits preserve data

Edit sends back existing pictures and geolocation so they are not lost

Token redaction

The token is registered as a secret and replaced with [REDACTED] in logs, errors, dry-run output and results, even if the server echoes it back. config show reports only tokenConfigured. The MCP server logs to stderr only

Network scope

Only EZBK_BASE_URL is contacted. The optional HTTP transport binds to 127.0.0.1 by default

Testing

npm run lint         # Biome
npm run typecheck    # tsc --noEmit
npm run coverage     # Vitest + V8 coverage (thresholds: 90% on all metrics)
npm run build        # esbuild bundles
npm run release      # build + release/ezbookkeeping-mcp-windows.zip

The suite has 553 tests in 20 files, all passing. Coverage on src/:

Lines

Branches

Functions

Statements

98.93%

93.63%

97.66%

98.93%

The tests cover:

  • unit tests for money, time, token, config, redaction, the parsers and the recurring detector;

  • every tool against success, the error envelope, 401/403/404/500, network failure, timeout, invalid input and empty data;

  • a real local HTTP mock of the v2.1.0 API with fictional fixtures, including Turkish names;

  • a 2,500-transaction dataset checked for complete pagination;

  • MCP protocol tests over in-memory, stdio and HTTP transports;

  • CLI JSON snapshots;

  • safety tests: exact ids, transfer direction, dry run sends nothing, and the token never appears in any output;

  • end-to-end tests of the built bundles.

Compatibility

  • ezBookkeeping v2.1.0: endpoints and field formats come from the v2.1.0 source. Every endpoint is listed with file and line references in docs/API_NOTES.md. Other versions may work but are untested.

  • Live check: a read-only smoke test passed against a live v2.1.0 instance on Windows 11 with Node.js 26.

  • Node.js: 18.17 or newer. CI runs on Ubuntu and Windows with Node 18, 20 and 22.

Known limitations

These points have only been tested against the mock server, not against a live instance:

  • Import (read_import_parse, write_import_transactions) needs EnableDataImport turned on in the ezBookkeeping server config. Otherwise it returns not_found. The CSV column-mapping options have not been exercised live.

  • Transfer incoming-side ids: without an account filter, the server returns only the outgoing side of transfers. Editing a transfer through its incoming-side id is untested.

  • Large ranges: the analysis tools read a whole date range with one transactions/list/all.json request. Performance on many years of data is unknown (read_cashflow caps ranges at about 10 years).

  • Installment notes: ezBookkeeping has no installment feature. read_installments parses free-text account comments with a documented, tolerant grammar, and returns notes in other formats as raw text.

  • Liability signs: liability balances are assumed to be negative when money is owed.

  • Balance adjustments: these transactions are treated as deltas when reconstructing cash-flow history.

Contributing

Issues and pull requests are welcome.

  1. Fork the repository and run npm install.

  2. Keep changes covered by tests. npm run check runs lint, typecheck, coverage (90% thresholds) and the build.

  3. Base any new endpoint on the ezBookkeeping source and cite it in docs/API_NOTES.md.

  4. Never commit tokens or personal financial data. Test fixtures must be fictional.

License

MIT © Ege İsmail Kösedağ

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Enables AI assistants to query and manage self-hosted accounting data—invoices, balances, and books—through natural language, with read-only tools by default and optional scoped write operations.
    30
    47 PyPI
    1
    AGPL 3.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to operate a self-hosted financial platform over MCP, managing books, expenses, revenues, settlements, payments, accounts, categories, transfers, positions, and cash flow with idempotent and audited writes.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables compatible AI clients to read stored personal-finance data and, with explicit user authorization, perform supported mutations under the same ownership checks as the web app.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to read, write, and audit personal finance data over a single SQLite file, covering transactions, receipts (including receipt photos as image blocks), budgets, categories, and line-item price tracking. Every write is an idempotent, soft-deleted, audited upsert so agents can safely transcribe and organize spending.
    9 npm
    MIT