Skip to main content
Glama
0xArchiviste

Finance Manager MCP

by 0xArchiviste
README.md
# Finance Manager MCP

Self-hosted personal finance for MCP clients. Your banks stay read-only (SimpleFIN), your data lives in a local SQLite file you own, and an LLM can only query deterministic totals — it never writes the ledger.

```
SimpleFIN (read-only)
    → raw_syncs (append-only JSON)
    → truth tables (accounts, transactions, rules, budgets)
    → warm-cache aggregates
    → atomic functions / MCP tools (stdio + gRPC)
```

## Why this shape

- **Idempotent ingest** — transactions are keyed on the bank-assigned SimpleFIN id. Re-syncing the same dump yields zero net-new rows; identical-looking new charges are never collapsed.
- **Deterministic categorization** — first-match-wins regex rules. No model in the write path.
- **Warm cache** — monthly spending, cash flow, net-worth snapshots, and recurring streams persist in SQLite so restarts are instantly warm. Cache is derived; `rebuild-cache` regenerates it from truth tables.
- **Profile UUID namespacing** — every write tool requires a `profile_id`. Multiple profiles never leak into each other.
- **Dual transport** — stdio for Claude Desktop / Cursor, gRPC via the sibling [GRPC-MCP](../GRPC-MCP) package.

## Setup

Python 3.11+.

```bash
python -m venv .venv
# Windows: .venv\Scripts\activate
# Unix:    source .venv/bin/activate
pip install -e ".[dev]"
# optional gRPC transport (sibling ../GRPC-MCP)
pip install -e "../GRPC-MCP/python"
pip install -e ".[grpc]"
```

```bash
cp .env.example .env
# paste a Fernet key into FINANCE_ENCRYPTION_KEY
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

finance-mcp init                          # creates data/finance.db + a default profile
finance-mcp profile list                  # copy the profile UUID
```

### Point it at SimpleFIN

The interactive path is the same from a terminal or an MCP client.

```bash
finance-mcp setup                  # prints the Bridge URL, waits for the pasted token, claims, syncs
finance-mcp setup --dry-run        # print the dialogue only (no claim)
finance-mcp setup --token "<setup-token>"
```

From an MCP client, ask to connect a bank. The agent should:

1. Call `begin_simplefin_connect` (or use prompt `connect_simplefin`).
2. Show you [bridge.simplefin.org](https://bridge.simplefin.org) — you sign in, connect banks (read-only), copy the one-time setup token.
3. Call `claim_connection` with that token. The access URL is encrypted at rest.
4. Optionally `sync_now` on the same `profile_id`.

`scripts/verify` exercises that dialogue with a scripted paste (no TTY, no network). `finance-mcp verify --setup-only` runs just those tests.

The older one-shot CLI still works:

```bash
finance-mcp claim-token --token "<setup-token>" --name "My Banks" --profile <PROFILE_UUID>
finance-mcp sync --profile <PROFILE_UUID>
```

`sync` is safe to run from Task Scheduler / cron. Overlapping windows do not double-count.

### Serve

```bash
finance-mcp serve-stdio                   # Claude Desktop / Cursor
finance-mcp serve-grpc --auth bearer      # sibling GRPC-MCP transport
finance-mcp token new --name cursor --profile <PROFILE_UUID>
```

The bearer token is printed once. gRPC stores only the SHA-256 hash.

## Atomic functions

Everything in `finance_mcp.api` is a plain function. MCP tools are thin wrappers. Import the library without running a server:

```python
from finance_mcp.config import Settings
from finance_mcp.core.db import Database
from finance_mcp import api

db = Database.from_settings(Settings.from_env())
print(api.net_worth(db, profile_id))
```

### Read

`list_accounts`, `get_balances`, `net_worth`, `net_worth_history`, `search_transactions`, `recent_transactions`, `spending_by_category`, `cash_flow`, `recurring_streams`, `upcoming_payments`, `budget_status`, `list_rules`, `sync_status`, `profile_info`, `list_profiles`, `begin_simplefin_connect`

### Write (always require `profile_id`)

`claim_connection`, `create_rule`, `update_rule`, `delete_rule`, `apply_rules`, `create_budget`, `update_budget`, `delete_budget`, `sync_now`

## Tests

Hermetic tests use synthetic fixtures only (no credentials, no network):

```bash
pytest -q -m "not live"
# or the wrapper, which is what to run as we develop:
python scripts/verify              # setup dialogue + hermetic suite
python scripts/verify --setup-only # interactive setup path only
# same thing via the CLI:
finance-mcp verify
finance-mcp setup --dry-run        # print the live dialogue without claiming
```

Live SimpleFIN checks (claim token → fetch → ingest → idempotent re-sync → read APIs) are opt-in. Put a one-time setup token or an already-claimed access URL in `.env`:

```bash
# SIMPLEFIN_SETUP_TOKEN=<paste setup token>
# or SIMPLEFIN_ACCESS_URL=https://user:pass@bridge…/simplefin
python scripts/verify --live
```

The first successful claim writes the access URL to gitignored `data/.simplefin_access_url` so later `--live` runs reuse it. The token itself is never committed.

```bash
pytest -q -m live          # live tests only; skipped if no credentials
```

## Security

- SimpleFIN access URLs are encrypted with `FINANCE_ENCRYPTION_KEY` (Fernet). Back the key up.
- gRPC bearer tokens are stored as hashes.
- Read tools open SQLite with `mode=ro`. Write tools are the only mutation path and are profile-scoped.
- `data/` and `.env` are gitignored.

## License

Use as you like for personal self-hosted finance.