fin
Local bank data for your AI assistant. Sync supported accounts from Plaid into SQLite, then ask about balances, spending, recurring charges, and cash flow through a JSON CLI or a local FastMCP server for Claude Desktop.
No hosted service, no money movement, no bank-write endpoints. The database and encrypted access tokens live on your machine. Financial results sent to a cloud-backed assistant still go to that assistant's model provider.
License: PolyForm Noncommercial 1.0.0. This is source-available software for permitted noncommercial use, not an OSI-approved open-source license.
What It Does
- Incrementally syncs transactions, reconciles pending charges, and records balance snapshots.
- Queries transactions, spending, cash flow, recurring streams, and supported credit/loan liabilities.
- Separates currencies and returns most monetary query values as decimal strings.
- Exposes 12 MCP tools over local stdio, with no listening port or remote deployment.
- Encrypts access tokens using Fernet, with the key held by the OS keychain.
- Supports existing Plaid Items without creating replacement connections.
- Provides bounded, read-only SQL with access-token and cursor columns blocked.
- Exports transactions to local CSV or JSON files on request.
Requirements
- Python 3.12 or newer.
- A Plaid account with access to the products and institutions you want to use.
- A working, unlocked OS keychain backend for Python
keyring. - A browser for bank linking; Claude Desktop is optional.
Windows uses the Credential Manager backend and private Windows ACLs. macOS normally uses Keychain. Linux needs a supported secret-service/keyring backend and an available user session. There is no plaintext token-key fallback.
Plaid's Trial plan currently includes Transactions, Recurring Transactions, Balance, and Liabilities, with a lifetime limit of 10 production Items. Duplicate connections consume slots; removing an Item does not restore one. Check Plaid's current terms before linking.
Install
git clone https://github.com/seanlnge/fin.git
cd fin
python -m venv .venv
Windows PowerShell:
.\.venv\Scripts\python.exe -m pip install -e ".[mcp]"
macOS/Linux:
.venv/bin/python -m pip install -e '.[mcp]'
The examples below use fin after activating the virtual environment. You can
instead use .venv/Scripts/fin.exe on Windows or .venv/bin/fin on macOS/Linux.
For CLI-only use, install with python -m pip install -e . using the venv Python;
FastMCP is an optional dependency.
Configure
Create a local .env using .env.example as the template:
PLAID_CLIENT_ID=your_client_id
PLAID_SECRET=your_environment_specific_secret
PLAID_ENV=sandbox
FIN_DB_PATH=
Use PLAID_ENV=production and your production secret for real bank connections,
including connections made on Plaid's Trial plan. Sandbox uses synthetic data.
The supported environments are sandbox and production; the default is sandbox.
- CLI commands load
.envfrom the current working directory. - Shell environment variables take precedence over
.env. - Without
FIN_DB_PATH, sandbox uses~/.fin/sandbox.db; production uses~/.fin/fin.db. - An explicit MCP
--env-fileanchors relative database paths to that file's directory. - Access tokens stored in
.envare not automatically imported. Import each Item explicitly.
Do not share .env, access tokens, database copies, exports, or keychain keys.
The repository ignores common local data and generated files, but review any
new file before publishing it.
Connect Accounts
Reuse an Existing Item
If you already have a Plaid access token from a quickstart or another local setup:
fin import-item
fin sync
fin status
The importer asks for the token through a hidden terminal prompt, looks up its Item and institution, and encrypts it in the local database. It does not create a new Item or use another Trial slot. Run it once per existing Item. Never pass tokens as command-line arguments or paste them into an assistant conversation.
Link a New Institution
fin link
fin sync
Fin opens Plaid Hosted Link in your browser. Complete the flow while the terminal waits. New links request up to 730 days of Transactions history, subject to bank availability. Fin refuses a second local Item for the same institution ID; separate legitimate logins at the same institution are not currently supported. That local guard cannot detect connections not yet imported into this database.
Import existing Items before linking new banks. Do not create a replacement connection simply to troubleshoot an existing one.
History Limits
Importing an Item preserves its original Transactions history window. If it was initialized with 90 days, import cannot extend it to six months or two years. Sync stores all history Plaid supplies; date filters apply when reading the cache.
HISTORICAL_UPDATE_COMPLETE means loading of the available history is complete,
not that a particular number of months is present. Check actual coverage:
fin sql "SELECT MIN(date) AS earliest, MAX(date) AS latest, COUNT(*) AS transactions FROM transactions"
If history is still loading, run fin sync again later. Sync does not force the
bank to produce newer transactions; Plaid normally refreshes on its own schedule.
Reauthentication and Liabilities Consent
Find the Item ID with fin status, then run:
fin link --update YOUR_ITEM_ID
fin sync --item YOUR_ITEM_ID
fin liabilities
Update mode reuses the existing Item and also requests additional Liabilities consent. It does not create a replacement Item. Liabilities availability depends on account type, bank support, and the permissions granted in the browser.
NO_LIABILITY_ACCOUNTS means Plaid found no supported liability accounts on that
Item. For example, an Item containing checking and savings does not necessarily
expose an installment plan associated with those accounts. Pay-in-4 plan
balances and schedules are not currently supported. A missing liability result
does not mean you have no debt.
CLI Examples
Commands return JSON on stdout; failures use a nonzero exit status. Help text and interactive setup prompts are exceptions to machine-readable command output.
fin status
fin accounts
fin sync --if-stale 6h
fin balances
fin balances --history 90d
fin tx --since 6mo --merchant Coffee --limit 200
fin tx --since 2026-09-01 --until 2026-09-30 --no-include-pending
fin search "Netflix"
fin spend --by category --period month --since 6mo
fin spend --by account --period day --since 30d
fin cashflow --period month --since 6mo
fin recurring --active
fin liabilities
fin sql "SELECT account_id, name, type, mask FROM accounts"
fin export --since 6mo --format csv --out exports/transactions.csv
fin balances --live explicitly requests bank-live balances and appends local
snapshots. Use it sparingly; normal sync uses cached balances from /accounts/get.
Exports include all matching rows rather than the transaction-list display limit,
and refuse to overwrite existing files or an open database.
Interpretation Rules
- Transaction and recurring query amounts are negative for outflow, positive for inflow. SQL exposes raw Plaid signs: positive outflow, negative inflow.
- Balances retain native signs. Positive checking is an asset; positive credit-card/loan balances represent debt.
- Currency groups remain separate. Most query money fields are strings rounded to cents; SQL and live balances may return numbers.
- Relative dates such as
1moare rolling windows, not the previous calendar month. Use explicit ISO dates for calendar periods; endpoints are inclusive. - Spending and cash-flow totals exclude pending transactions and heuristic transfers by default.
--include-transfersand--include-pendingintentionally override that behavior. - Transfer categories do not prove account ownership. There is no persistent manual transfer override yet.
- Refunds offset spending. Credit-card repayments are excluded by the transfer heuristic to avoid counting purchases twice.
tx --minand--maxfilter absolute amount magnitude. Account selectors accept an exact ID or an unambiguous name/mask.- Transaction/search results may be truncated; inspect metadata. Read-only SQL has a 1,000-row cap and an execution budget.
- Daily balance history is built from snapshots collected after you start using fin. It is not backfilled from historical transactions.
Claude Desktop
Install the mcp extra first. Add a fin entry under mcpServers in Claude
Desktop's configuration, preserving any existing servers and settings.
Windows example; replace the example paths with your checkout's absolute paths:
{
"mcpServers": {
"fin": {
"command": "C:\\path\\to\\fin\\.venv\\Scripts\\python.exe",
"args": [
"-m", "fin.mcp_server",
"--env-file", "C:\\path\\to\\fin\\.env"
],
"env": {"PYTHONUNBUFFERED": "1"}
}
}
}
The Windows config normally lives at %APPDATA%/Claude/claude_desktop_config.json.
On macOS it normally lives at
~/Library/Application Support/Claude/claude_desktop_config.json; use the absolute
.venv/bin/python path there. No credentials belong in this configuration.
Fully quit and reopen Claude Desktop after changing its configuration or the
project's .env. The server loads configuration at startup; an older process can
still be pointing at sandbox while the CLI is using production. No working-directory
setting is required when --env-file is absolute.
Try: "Call fin_status and list my linked institutions." Then ask about
spending, recurring charges, or cash flow. The server supplies instructions and
the fin://schema resource without requiring a separate skill installation.
Available Tools
fin_status,fin_accounts,fin_balances: cached account information and freshness.fin_tx,fin_search: filtered transactions and literal merchant/name search.fin_spend,fin_cashflow: grouped spending and income/expense summaries.fin_recurring,fin_liabilities: supported recurring streams and debt details.fin_sql: bounded, read-only SELECT queries with credential columns blocked.fin_sync: refresh the cache, with a default freshness threshold of six hours.fin_live_balances: explicitly requested live bank balances.
MCP has no linking, token-import, file-export, or payment tools. Linking and import are manual terminal operations. Sync/live-balance tools update the local cache but never write to the bank. Stdio is the only supported server transport.
Optional Agent Skill
skill/SKILL.md provides additional tool-selection, interpretation, and safety instructions. From a source checkout:
python scripts/install_skill.py
The installer targets ~/.claude/skills/fin/SKILL.md; use --target PATH for a
different skill directory. It refuses to overwrite an existing skill. Skill
discovery depends on the client; installing the skill does not register an MCP
server. The skill expects the fin MCP tools, not shell access.
Security and Limitations
This is a single-user local tool, not a hosted financial service or a complete accounting system. There are no money-movement endpoints. SQLite query connections are read-only, and SQL authorization blocks access tokens and sync cursors.
Access tokens are encrypted, but transaction data and balances are not encrypted at rest. Use OS/full-disk protection as appropriate. The database may contain sensitive raw transaction and liability payloads. Exports are plaintext and are not sanitized for public sharing.
Protect both the database and OS keychain. A database backup alone does not let
another machine decrypt tokens without the corresponding key. Do not manually
replace or delete a key while keeping encrypted Items. Existing plaintext tokens
in a previous setup's .env are not removed automatically after import.
Financial data returned to an assistant can leave your machine through its model provider. Merchant names and bank descriptions are untrusted input, not agent instructions. Never include real financial records, tokens, or raw API errors in GitHub issues; report sanitized codes and synthetic reproductions instead.
Recurring and liabilities refresh independently, normally once per day. Missing or unsupported optional products do not roll back transaction sync. An Item may report successful transactions alongside optional-product warnings. Always inspect freshness and history readiness before treating results as complete.
Development
python -m pip install -e '.[test,mcp]'
python -m pytest -q
The default suite uses synthetic fixtures and fake Plaid clients, including an actual local MCP stdio handshake. It makes no live Plaid calls. GitHub Actions runs it on Windows and Linux with Python 3.12 and 3.13.
A separate Sandbox smoke test is opt-in. In a dedicated shell with matching
Sandbox credentials, set FIN_RUN_SANDBOX_TESTS=1 and PLAID_ENV=sandbox, then run:
python -m pytest tests/test_sandbox.py -q
That test creates and removes a synthetic Sandbox Item; it refuses Production. Do not opt into it against real bank credentials.
Repository Layout
fin/ Python package: CLI, configuration, database, sync, MCP
skill/SKILL.md Reusable agent instructions for the MCP tools
scripts/ Optional skill installer
tests/ Offline regressions and opt-in Sandbox smoke test
docs/spec.md Original design proposal, kept as historical context
.github/workflows/ Offline test matrix
.env.example Configuration template with no credentials
The legacy Plaid quickstart/ checkout, local .env, virtual environment,
databases, and exports are intentionally excluded from version control.
License
Copyright (c) 2026 Sean Lange. The code, skill, and documentation are provided under the PolyForm Noncommercial License 1.0.0, with the required copyright notice in NOTICE. Permitted noncommercial use, modification, and redistribution are subject to that license. Commercial use requires separate permission from the copyright holder. Third-party dependencies retain their own licenses. This project is not affiliated with or endorsed by Plaid.