Skip to main content
Glama
dfrysinger

finance-mcp

by dfrysinger
README.md
# finance-mcp

Local, privacy-first access to your bank and credit-card transactions for
Copilot, backed by [SimpleFIN](https://www.simplefin.org/). It pulls transactions
from your institutions into a normalized on-disk cache and exposes them to Copilot
as an MCP server. Nothing leaves your machine except the single HTTPS call to
SimpleFIN — no third-party SaaS sees your data, and no fake/sample data is ever
shown.

## Why SimpleFIN

All of these institutions are supported by SimpleFIN (verified against its live
institution search): Target credit card, Nordstrom Card Services, Amazon Chase
card (via Chase Bank), Fidelity credit card, Fidelity NetBenefits (via Fidelity
Investments), Charles Schwab, and Cyprus Credit Union. SimpleFIN costs ~$15/yr
flat with no per-account billing, and you hold the access token.

## Security model

The SimpleFIN **access URL embeds Basic-Auth credentials** that can read your
transactions. It is therefore stored **outside** this project directory (this repo
may live in a synced folder like Dropbox):

- Access URL + transaction cache live in `~/.finance-mcp/` (dir `0700`, files `0600`).
- Override the location with `FINANCE_MCP_HOME`.
- The access URL may instead be supplied via the `SIMPLEFIN_ACCESS_URL` env var
  (takes precedence over the saved file), so it never has to touch disk.

## Setup

1. Get a SimpleFIN **setup token**: sign up at
   <https://bridge.simplefin.org/> and generate one (it is a base64 string).

2. Claim it (one-time — the token dies after a successful claim):

   ```bash
   uv run finance-mcp claim            # prompts for the token
   # or: uv run finance-mcp claim <SETUP_TOKEN>
   ```

   The resulting access URL is saved to `~/.finance-mcp/access_url` (mode 0600).

3. Pull your transactions into the cache:

   ```bash
   uv run finance-mcp sync --days 120
   ```

## CLI

```bash
uv run finance-mcp accounts                          # balances per account
uv run finance-mcp transactions --search grocery     # search the full archive
uv run finance-mcp transactions --start 2026-01-01 --account <id> --json
uv run finance-mcp summary --group-by month          # inflow/outflow aggregation
uv run finance-mcp summary                            # defaults to group-by category, excludes transfers
uv run finance-mcp networth                          # net-worth total per snapshot date
uv run finance-mcp stats                             # archive size + date coverage
uv run finance-mcp categorize                        # seed default rules + show coverage
uv run finance-mcp uncategorized                     # top still-uncategorized merchants
uv run finance-mcp rules list                        # show categorization rules
uv run finance-mcp rules add --pattern "trader joe" --category Groceries
uv run finance-mcp rules rm --rule-id <id>           # remove a rule
uv run finance-mcp set-category <txn_id> Travel      # pin one transaction's category
uv run finance-mcp sync --days 120                    # refresh from SimpleFIN
uv run finance-mcp subscriptions detect               # save recurring charges as a tracked list
uv run finance-mcp subscriptions                      # audit: did tracked bills post? + new candidates
uv run finance-mcp subscriptions mark --name Sketch --lifecycle canceled --effective 2026-04-01
uv run finance-mcp web                                # local read-only review UI in the browser
```

`subscriptions detect` scans the archive for recurring monthly charges and saves
them into the budget config so your subscriptions become a durable list rather
than something re-inferred on every run; it is idempotent and skips merchants
already tracked. `subscriptions` (audit) then reports any tracked bill that did
not post in its cycle — a possible billing problem or cancellation — and surfaces
untracked recurring merchants as candidates to add. A saved subscription needs
no envelope budget: a `match` keyword pins it to its merchant. Detection is a
heuristic starting point — review the saved list and remove any false positives.

`subscriptions mark` is the cancellation watch: after you cancel (or try to
cancel) a subscription, mark it `canceling` (attempted, unconfirmed) or
`canceled` (confirmed) with the effective date. The audit then stops reporting
its expected charges as missing — the absence is what you wanted — and instead
warns you if a charge posts on or after that date, so a cancellation that didn't
take is surfaced rather than silently billed. `--lifecycle active` reactivates a
bill. Recurring bills and subscriptions are one list: anything in the budget's
`recurring` calendar can be watched this way.

SimpleFIN caps a request at 90 days and expects <=24 requests/day, so `sync`
chunks long ranges into <=89-day windows and you should rely on the archive for
day-to-day queries rather than re-syncing constantly. Any SimpleFIN warnings or
errors (`errors`/`errlist`) are always surfaced.

## Web UI (review in the browser)

`finance-mcp web` starts a local, read-only web UI for reviewing the archive and
the budgeting reports without leaving the terminal-driven workflow:

```bash
uv run finance-mcp web                 # serves http://127.0.0.1:8765/
uv run finance-mcp web --port 9000     # pick a different port
```

It binds to `127.0.0.1` only by default because it serves private financial data
(override with `--host` only if you understand the exposure). The request Host
header is always checked against an allowlist, so a `127.0.0.1` bind is not
defeated by DNS-rebinding; to reach a non-loopback or wildcard bind from another
device, name that device-facing host with `--allow-host` (every other Host is
refused). Every page is backed by the same functions the MCP server exposes, so
the browser view and Copilot see identical data, and nothing is mutated — there
is no sync or confirm action in the UI. Tabs cover accounts, transactions,
spending, net worth, transfers, and the burn-down / forecast / allocation /
subscription reports; each view also exposes the raw JSON it rendered.

## Local archive (multi-year history)

Every `sync` does two things locally, both in `~/.finance-mcp/` (mode `0600`):

- updates `cache.json` — the latest normalized snapshot, and
- folds the data into `archive.db`, a **SQLite** database that is the durable,
  searchable, multi-year history.

The archive is **append-only**: transactions are upserted by their stable
SimpleFIN id (a pending charge is later promoted to posted without duplicating),
`first_seen` is preserved, and nothing is ever deleted — so a transaction stays
in the archive even after it ages out of SimpleFIN's rolling window. Each sync
also records a **balance snapshot** per account, which is what powers
`networth` / `net_worth_history` trends over time.

All read commands and MCP tools serve from `archive.db` (falling back to
`cache.json` only before the first sync on this version). You can also query it
with any SQLite tool:

```bash
sqlite3 ~/.finance-mcp/archive.db \
  "SELECT posted, amount, description FROM transactions ORDER BY posted_ts DESC LIMIT 10;"
```


## MCP server (use it from Copilot)

The server runs over stdio and exposes these tools:

| Tool | Network? | Purpose |
|------|----------|---------|
| `list_accounts` | no | accounts + balances + institution |
| `account_balances` | no | just balances and as-of dates |
| `get_transactions` | no | filter by date / account / search / amount (includes category) |
| `spending_summary` | no | inflow/outflow grouped by category, account, org, or month |
| `categorization_status` | no | category coverage + spend-by-category breakdown |
| `list_category_rules` | no | the active substring → category rules |
| `add_category_rule` | no | add a rule (optionally flag matches as transfers) |
| `remove_category_rule` | no | delete a rule by id |
| `set_transaction_category` | no | pin one transaction's category (survives sync) |
| `net_worth_history` | no | total balance per snapshot date (net-worth trend) |
| `archive_stats` | no | archive size + earliest/latest transaction |
| `sync_now` | **yes** | refresh the cache + archive from SimpleFIN |

### Install for others (no clone needed)

With [uv](https://docs.astral.sh/uv/) installed, you can claim/sync and run the
server straight from this repo — `uvx` fetches and builds it on demand:

```bash
# one-time claim + first sync
uvx --from git+https://github.com/dfrysinger/finance-mcp finance-mcp claim
uvx --from git+https://github.com/dfrysinger/finance-mcp finance-mcp sync --days 120
```

Then register it with Copilot CLI by adding this to `~/.copilot/mcp-config.json`
under `mcpServers` (or run `/mcp` in Copilot to manage):

```json
{
  "mcpServers": {
    "finance": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/dfrysinger/finance-mcp",
        "finance-mcp-server"
      ],
      "tools": ["*"]
    }
  }
}
```

### Local checkout alternative

If you cloned the repo and prefer running from the working tree:

```json
{
  "mcpServers": {
    "finance": {
      "command": "uv",
      "args": ["--directory", "<ABSOLUTE_PATH_TO>/finance-mcp", "run", "finance-mcp-server"],
      "tools": ["*"]
    }
  }
}
```

`get_transactions`/`spending_summary` serve the durable archive (`archive.db`),
which `sync` (CLI) or `sync_now` keeps up to date.

## Notes / limitations

- SimpleFIN does not provide spending **categories**, so categories are assigned
  locally by a rule engine (case-insensitive substring match on description/payee)
  plus per-transaction manual overrides — nothing is guessed from outside your data.
  Internal movements (transfers, card payments, P2P) are flagged so honest spend
  totals exclude them; pass `--include-transfers` / `include_transfers=true` to count them.
- Amounts are signed: negative = money out. Use `max_amount=0` for spending-only,
  `min_amount=0` for income-only.

## Development

```bash
uv run pytest -q
```

Verified end-to-end against SimpleFIN's public demo dataset (claim → fetch →
normalize → cache → query, plus an MCP stdio round-trip).

TDQS

A3.9/5.0

Scored across 22 tools

Disambiguation5/5

Each tool targets a distinct function or domain: accounts, transactions, categories, transfers, subscriptions, budgets, audits, etc. Overlaps are minimal (e.g., multiple audit reports but for different areas) and descriptions are detailed enough to disambiguate.

Naming Consistency5/5

Tool names consistently use snake_case and follow a descriptive noun_verb or noun_noun pattern (e.g., list_accounts, sync_now, budget_burndown). No mixing of conventions or cryptic abbreviations.

Tool Count4/5

22 tools is above average but justified given the broad domain of personal finance (syncing, accounts, transactions, categories, budgets, transfers, subscriptions, audits). Each tool serves a specific purpose; however, a few report tools could potentially be consolidated.

Completeness4/5

The tool surface covers core operations (sync, read, categorize, budget, audit) and advanced features (subscription detection, transfer reconciliation). Missing is the ability to create/update/delete transactions or budgets directly, but the server is primarily analytical, so this is acceptable.

Maintenance

ActivityStale
ResponsivenessWithin a week