AgentStack
MCP verified MIT Self-run

Goetta Finance Mcp

mcp-griffin-goepper-goetta-finance-mcp · by griffin-goepper

Goetta is your goetta partner to meeting your goetta goals

No reviews yet
0 installs
2 views
0.0% view→install

Install

$ agentstack add mcp-griffin-goepper-goetta-finance-mcp

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README — it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-griffin-goepper-goetta-finance-mcp)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
11d ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Goetta Finance Mcp? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

goetta-finance

[](https://pypi.org/project/goetta-finance/) [](https://github.com/griffin-goepper/goetta-finance-mcp/actions/workflows/ci.yml) [](https://github.com/griffin-goepper/goetta-finance-mcp/actions/workflows/security.yml) [](./LICENSE)

[](https://github.com/astral-sh/ruff)

A local-first tool that connects SimpleFIN to Claude. Your bank data lives only on your machine, in a DuckDB file you own. Claude reads it through an MCP server; you read it through a small web dashboard at localhost:8765.

See [PROJECT_PLAN.md](./PROJECT_PLAN.md) for the full vision and roadmap.

Requirements

  • Python 3.11+
  • A SimpleFIN Bridge account (about $1.50/month) — sign up at

Install

pipx install goetta-finance      # recommended
# or:  pip install goetta-finance

pipx is the cleanest way to install a Python CLI: it lives in its own isolated environment and puts the goetta-finance command on your PATH. That global path is also the stable thing to point Claude Desktop's MCP config at — no project venv path to chase.

From source (for development)

git clone https://github.com/griffin-goepper/goetta-finance-mcp.git
cd goetta-finance-mcp
python -m venv .venv
.venv/Scripts/activate         # Windows
# source .venv/bin/activate    # macOS/Linux
pip install -e ".[dev]"

Quick start

goetta-finance init       # interactive setup wizard
goetta-finance sync       # pull fresh data from SimpleFIN
goetta-finance web        # open the dashboard at http://127.0.0.1:8765

The init wizard walks four steps:

  1. Claim a SimpleFIN setup token → access URL (stored at ~/.local/share/goetta-finance/config.json, mode 0600 on POSIX).
  2. Initialize the DuckDB store.
  3. Pull initial history (up to 90 days).
  4. Auto-write the Claude Desktop MCP config (merges into the existing file; preserves any other servers you've configured).

Re-running init is safe — each step detects existing state and offers to skip or replace.

Commands

| Command | What it does | |---|---| | goetta-finance init | Interactive setup. Re-runnable. | | goetta-finance sync | One-shot pull from SimpleFIN. Idempotent — safe to run repeatedly. | | goetta-finance status | Show last sync, account list with balances, recent warnings/errors. | | goetta-finance serve | Start the MCP server over stdio (used by Claude Desktop). | | goetta-finance web | Start the local web dashboard. --port 8765 and --host 127.0.0.1 by default. | | goetta-finance daemon | Long-lived process: dashboard + MCP HTTP endpoint + daily scheduled sync, from one process. See "Daemon mode" below. | | goetta-finance account add\|list\|set-balance\|set-liability\|remove | Manage manual accounts and liability flags. See "Manual accounts and liabilities" below. | | goetta-finance account link\|links\|unlink | Roll a manual account's balance forward from matching transfers on a synced account. See "Linked transfers" below. | | goetta-finance category list\|add\|set-rule\|remove-rule\|default-rules | Manage categories and the rules that map descriptions to them. See "Transaction categorization" below. | | goetta-finance transaction categorize\|uncategorize | Manual per-transaction category overrides. See "Transaction categorization" below. | | goetta-finance goal add-spending\|add-balance\|list\|remove | Spending caps and balance targets, evaluated at read time. See "Goals" below. |

Manual accounts and liabilities

SimpleFIN can't reach every account — 401(k) providers, HSAs, brokerages outside its bank list, and student-loan servicers all sit outside. goetta-finance account lets you track those by hand so they show up in MCP queries and the dashboard alongside synced accounts.

The four subcommands

# Create a manual account. Prompts interactively for any missing flags.
goetta-finance account add \
  --name "Apple Savings" \
  --org "Apple" \
  --type savings \
  --balance 30000 \
  [--as-of 2026-05-17]     # observation date, defaults to today (UTC)

# Mark an account as a liability (or clear the flag). Works on any account id.
goetta-finance account add ... --liability      # at creation time
goetta-finance account set-liability MANUAL- true     # after the fact
goetta-finance account set-liability ACT- true   # SimpleFIN accounts too

# Update the balance on a manual account (also writes a balance_snapshot).
goetta-finance account set-balance MANUAL- 32500 [--as-of 2026-05-17]

# List all accounts. Manual + liability rows are tagged in the output.
goetta-finance account list

# Remove a manual account. Two layers of safety: cascade-delete its snapshots
# (--force) AND type the account name to confirm (skip with --yes for scripts).
goetta-finance account remove MANUAL- --force

Sign convention for liabilities

A liability always reduces net worth, regardless of how the source signs the balance. The signed-balance formula is:

CASE WHEN is_liability THEN -ABS(balance) ELSE balance END

So you can enter a student loan either way and the math comes out right:

  • account add --type loan --balance 22500 --liability (positive amount owed)
  • A SimpleFIN credit card showing balance = -500 and you've flipped it to is_liability=true

Both contribute -500 and -22500 respectively to net worth — collapsing the loan-servicer convention and the SimpleFIN convention to one answer. The dashboard's net-worth chart and the accounts page footer respect the formula. When writing sql_query SELECTs against the accounts table, reach for the same CASE WHEN expression to compute totals correctly.

is_liability is independent of type on purpose — type describes what kind of account it is (loan, credit, investment), while is_liability controls how net-worth math treats it. A margin account is type=investment but functionally a liability; you can flip the flag without changing the type.

Linked transfers

Manual balances go stale the moment money actually moves — but the contributions are usually already in your data, as the transfer legs on a synced checking account. A transfer link connects the two so the manual balance rolls forward automatically:

# See what looks linkable. Candidates are detected when a synced account's
# transactions carry a payee exactly matching a manual account's name —
# each comes with the ready-made link command.
goetta-finance account links

# Link it: from then on, matching transactions on the synced account roll
# the manual balance forward on every sync (and immediately at link time).
goetta-finance account link MANUAL- --from ACT- \
  --pattern "Apple Savings" [--match contains|regex]

# Remove a link by id. Already-applied transfers stay in the balance.
goetta-finance account unlink 

How it behaves:

  • A debit out of the source credits the manual account (and money moving back debits it). The pattern is matched against the transaction's payee and description; pending transactions wait until they settle.
  • Everything posted at or before the account's balance date is assumed to already be in that balance — linking never double-counts history, and an applications ledger guarantees each transaction applies at most once ever, across re-syncs and re-links.
  • set-balance stays the true-up. Transfer sums can't see interest, so update the balance from a real statement occasionally: the true-up re-anchors the link at its --as-of date and re-applies anything posted after it against your fresh number.
  • Every consumer updates for free. The roll-forward writes a genuine balance + snapshot through the same path as set-balance, so net worth, the over-time chart, balance goals, and the goal pace math all follow without special cases.
  • Liability accounts can't be linked yet — a manual loan's stored sign is ambiguous (everything reads it through ABS()), so paydown tracking still goes through set-balance true-ups.

Heads-up

  • Retroactive flag. Toggling is_liability re-treats all historical balance_snapshots for that account under the new value in net-worth-over-time charts. This is almost always what you want; if it isn't, flip the flag back.
  • CC-credit edge case. A credit card with is_liability=true and a positive balance (you overpaid and now have a credit) computes as -balance instead of +balance. Rare; set-liability false while the credit exists, then re-enable, is the workaround.
  • Balance is authoritative. Payments to a manual loan don't auto-decrement the balance — re-run set-balance from your servicer's monthly statement. For asset accounts fed by visible transfers, a linked transfer (above) automates exactly that.

Transaction categorization

Every transaction resolves to a category at read time through a SQL view (transactions_with_category). Three layers, outermost wins:

  1. Manual override — a row in transaction_overrides for that transaction id.
  2. Rule match — the lowest-priority rule in category_rules whose pattern matches the transaction's description and whose optional amount bounds (compared against the absolute amount; min inclusive, max exclusive) are satisfied.
  3. Uncategorized — the fallback when nothing else matches.

Read-time resolution is the feature, not an optimization: adding or editing a rule applies retroactively to every existing transaction with zero data migration. A category_id column on transactions would silently break that.

Migration 0004 ships 14 default categories (Groceries, Dining, Transportation, Gas, Utilities, Subscriptions, Rent/Mortgage, Healthcare, Entertainment, Shopping, Travel, Transfers, Income, Uncategorized). Migration 0007 trims the default rule seed to a deliberately minimal universal set: a single (?i)transfer regex → Transfers (every bank uses "transfer" somewhere in inter-account descriptions) and five global subscriptions (Spotify, Netflix, Hulu, Disney Plus, Amazon Prime). Earlier versions shipped 38 US-merchant-specific defaults (Kroger, Starbucks, Shell, etc.) — they were noise for non-US users and bias for the rest. Expect most of your spending to land in Uncategorized on first install. That's the design: curate by adding rules for your descriptions. The MCP top_uncategorized_patterns tool (or the category set-rule CLI) is the curation path.

CLI

# Inspect what was seeded vs. what you've added.
goetta-finance category list                 # all categories with txn + rule counts
goetta-finance category default-rules        # the is_default=TRUE rule set

# Add a rule. Pattern matches case-insensitively against transaction description.
goetta-finance category set-rule Dining --match contains --pattern 'CHIPOTLE'
goetta-finance category set-rule Dining --match regex --pattern '(?i)venmo.*lunch'

# Amount bounds refine a pattern match by abs(amount) — dual-use merchants
# split cleanly (small gas-station buys are snacks, big ones are fuel).
# min is inclusive, max exclusive: no gap or overlap at exactly $20.
goetta-finance category set-rule Vice --pattern 'SPEEDWAY' --max-amount 20
goetta-finance category set-rule Gas  --pattern 'SPEEDWAY' --min-amount 20

# Remove a rule. Defaults require --force AND a typed-pattern confirmation;
# user-added rules just need the id.
goetta-finance category remove-rule 42
goetta-finance category remove-rule 7 --force        # default rule, prompts for the pattern

# Add a custom category.
goetta-finance category add --name "Gardening" --color "#4ade80"

# Recategorize a single transaction (manual override beats any rule).
goetta-finance transaction categorize  Groceries
goetta-finance transaction uncategorize      # back to rule resolution

# Category names are case-insensitive ("dining" → "Dining") and typos get
# a "Did you mean?" suggestion via difflib.

From Claude

The spending_by_category(start, end) MCP tool aggregates per-category totals over a date range. By default it returns spending only (amount You: "what's still uncategorized this month?" > Claude calls top_uncategorized_patterns → "$85 CRUMBL COOKIES (3×), $60 NEW GYM LLC (2×)..." > You: "Crumbl is dining, the gym is healthcare" > Claude calls add_category_rule twice. Rules apply retroactively; done.

One-off fixes use categorize_transaction (override beats any rule) and uncategorize_transaction (undo). The MCP rule-write path runs the same pattern validation as the CLI.

For anything custom, query the view directly via sql_query:

SELECT category, COUNT(*), SUM(-amount) AS total
FROM transactions_with_category
WHERE posted >= '2026-01-01' AND amount `). Both surfaces run the same best-effort validator (refuses uncompilable regexes, nested quantifiers like `(X+)+`, large counted repetitions like `(.*a){25}`) but CPython's `re` engine doesn't release the GIL so a runtime regex timeout isn't possible. The load-bearing runtime defense is the existing `query_sql` statement-timeout watchdog (`GOETTA_FINANCE_SQL_TIMEOUT_SECONDS`, default 30s). See [`CLAUDE.md`](./CLAUDE.md) for the threat model.
- **One category per transaction.** Costco-style mixed purchases get one label. No splits in v1.
- **Default rules don't re-seed if you delete them.** Migrations run once per database; the slate stays where you leave it. New defaults arrive only via new migration files — never edits to shipped ones.

See [`CUSTOMIZATION.md`](./CUSTOMIZATION.md) for the full map of user-tunable surfaces (rules, prefix list, categories, flags, colors).

## Goals

Lightweight thresholds, not envelope budgeting: cap a category's spending per calendar month/year, or track an account balance toward a target. Progress is **computed at read time** — nothing is stored, so recategorizing transactions or a fresh sync changes goal progress retroactively, exactly like the categorization view.

```bash
# Cap net spending in a category per calendar month (or --period year).
goetta-finance goal add-spending Groceries --limit 400 --period month

# Track a balance: at_least = savings target / emergency-fund floor,
# at_most = debt ceiling / paydown. --by adds required-per-month pace math.
goetta-finance goal add-balance  --target 10000 --direction at_least --by 2027-06-01
goetta-finance goal add-balance  --target 2000 --direction at_most

goetta-finance goal list          # progress, status, and pace per goal
goetta-finance goal remove 3      # confirms unless --yes

Semantics worth knowing:

  • Cap math matches the pie exactly. Spending caps reuse the same net-spending SQL as spending_by_category and the dashboard pie: refunds reduce the total, hidden accounts are excluded, pending transactions count, and periods are UTC calendar buckets.
  • Liability accounts evaluate the absolute balance (amount owed): --direction at_most --target 2000 on a credit card means "owe under 2000" whichever way the institution signs the balance.
  • Status is on_track / at_risk (ahead of linear pace, or trend projects past --by) / over (cap blown, ceiling breached) / met. Balance goals derive pace from the last 90 days of balance snapshots.
  • Breach summary after sync. goetta-finance sync prints a yellow goal: line for each goal at status over; the daemon logs the same at WARNING after scheduled syncs. at_risk never fires a warning — it's pace noise by design.
  • From Claude: list_goals (progress + pace), set_goal, remove_goal. The dashboard has a Goals page with progress bars.

Daemon mode

goetta-finance daemon runs one long-lived process that hosts:

  • The dashboard at http://127.0.0.1:8765/
  • The MCP endpoint at http://127.0.0.1:8765/api/mcp (streamable-HTTP transport — Claude Code and Claude Desktop both support this)
  • An internal scheduler that runs collect() daily at --sync-at local time (default `06:00

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.