financial-agent
The financial-agent server is a local MCP server for managing personal finances with a deterministic, evidence-backed pipeline — every number traced to a source row, never invented. Key capabilities include:
Data Ingestion: Sync accounts, balances, and transactions from SimpleFIN; record manual balance snapshots; import card statements (e.g. Apple Card CSV); and pull Todoist tasks into the local DB.
Financial Status & Cash-Flow Modeling: Generate deterministic day-by-day cash-flow projections, human-readable daily digests (working cash, upcoming obligations, drift, guardrail status), and spending summaries by category, merchant, or month.
Obligations & Income: Create, update, and list obligations and their dated instances; manage income sources and schedules; generate dated income instances; and import calendar/pay-date facts.
Recurring Charge Onboarding: Scan transaction history for recurring charge patterns, work a prioritized review queue, record decisions, preview and apply candidates as canonical obligations, and auto-apply high-confidence charges.
Statement Cycle Management: Aggregate card charges into statement cycles, recompute estimates from card-input rollups (never overwrites confirmed amounts).
Reconciliation & Drift Detection: Match expected obligation instances to observed transactions, confirm/reverse matches (never auto-pays), detect missing payments, stale estimates, amount changes, and unmodeled charges.
Guardrails: Evaluate operating rules (cash floor, drift threshold, debt avalanche order) with severity-ordered findings.
Goals & Follow-ups: Track savings targets with pace tracking; manage dated reminders; surface a prioritized queue of everything needing attention today.
Todoist Integration: Idempotently push due items to Todoist via an emissions ledger, reconcile task completions back into the model, import Todoist tasks as one-off obligations, and manage individual tasks (write-gated).
Verification: Run deterministic SQL/Python identity checks (projection identity, duplicate instances, statement identity, sign sanity) and optionally spawn an independent adversarial AI reviewer to flag risky forecast areas.
Memory: Store and retrieve corrections, decisions, and facts using bag-of-words similarity search.
Background Automation: Orchestrate the full pipeline (sync → scan → reconcile → drift → verify → surface) as one auditable run with a step-by-step event log; inspect job health and run history.
Migration & Validation: Seed a fresh DB from legacy files, validate the pipeline on a throwaway copy of live data, and diff new digests against legacy cash-flow reports for parity.
Allows managing finance review tasks via Todoist, including reading tasks, converting them to obligations, and writing review batches as Todoist tasks (with dry-run by default).
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@financial-agent what's my projected cash flow for the next month?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
personal-finance-agent
A local Model Context Protocol (MCP) server that gives an AI agent grounded, evidence-backed tools for managing personal finances.
It is deterministic and it never invents numbers. Every balance, due date, projection endpoint, and cash-flow figure the agent reports is backed by a tool result computed over a local SQLite database — not estimated, not recalled from a prior message. A built-in grounding check (verify_grounding) exists specifically to confirm that each headline dollar figure traces back to a source row before the agent is allowed to state it.
The server runs entirely on your machine, talks to your own data sources (a bank-aggregation feed and a task manager), and exposes that data to an MCP client (such as Claude Code) as a catalog of finance tools.
Architecture at a glance
flowchart LR
Claude["Claude / MCP client"] <-->|"tool calls"| Server["Finance MCP Server<br/>90 tools"]
SimpleFIN["SimpleFIN<br/>balances + transactions"] -->|"sync_simplefin"| Server
Portals["Bank/card portals<br/>manual balance inputs"] -->|"set_manual_balance"| Server
Server <-->|"read/write source rows"| SQLite[("SQLite finance DB<br/>balances, transactions, obligations, instances")]
Server <-->|"decisions, corrections, facts"| Memory[("finance_memory")]
Server -->|"surface_due_items_to_todoist<br/>deduped by emissions ledger"| Todoist["Todoist Finance project"]
Todoist -->|"completion read-back"| Server
classDef client fill:#e7eff7,stroke:#1f4e79,color:#17202a
classDef server fill:#e8f2ec,stroke:#3d7b65,color:#17202a
classDef source fill:#f5ecdd,stroke:#b87922,color:#17202a
classDef output fill:#f4e6ea,stroke:#aa4a5d,color:#17202a
classDef store fill:#edf0f2,stroke:#2f3b47,color:#17202a
class Claude client
class Server server
class SimpleFIN,Portals source
class Todoist output
class SQLite,Memory storeflowchart LR
subgraph Ingest["1. INGEST"]
Sync["sync_simplefin<br/>pull balances + transactions"]
Manual["set_manual_balance<br/>correct stale balance-only feeds"]
CardPaste["import_card_statement<br/>paste a card statement (dry-run by default)"]
ReadBack["reconcile_todoist_completions<br/>absorb task completions"]
end
subgraph Model["2. MODEL"]
Candidates["scan_charge_onboarding_candidates<br/>discover recurring charges"]
Reconcile["reconcile_obligation_instances<br/>match expected to observed"]
Projection["get_finance_status / get_daily_digest<br/>project cash flow from obligation_instances"]
Guardrails["evaluate_guardrails<br/>cash floor, drift, debt order"]
end
subgraph Surface["3. SURFACE"]
Queue["get_surface_queue<br/>prioritize what needs attention"]
Digest["get_daily_digest<br/>status, working cash, upcoming obligations"]
TodoistPush["surface_due_items_to_todoist<br/>write-gated, deduped output"]
end
Ingest --> Model --> Surface
Surface -. "next daily run" .-> IngestThe post-facto design record lives in docs/design-doc.md, and more durable Mermaid diagrams live in docs/diagrams.md.
Related MCP server: finledger
What it does: INGEST -> MODEL -> SURFACE
The server implements a single loop. Each stage is deterministic and idempotent, and none of it mutates the original source database.
1. INGEST — pull the facts in
SimpleFIN sync (
sync_simplefin) pulls live accounts, balances, and transactions into the local DB via idempotent upsert (default 90-day window;incrementalmode resumes from the last synced transactions so a daily run stays cheap). Read-only against SimpleFIN.Manual balance snapshots (
set_manual_balance) handle balance-only feeds that refresh slowly — for example a card whose portal shows "Updated Monthly". A manual snapshot is written as an ordinarybalance_snapshotsrow (source='manual') and is treated as authoritative for its calendar day, so the agent reads current reality instead of a stale feed value.Card-statement paste (
import_card_statement) fills the balance-only blind spot for cards with no live transaction feed (for example the Apple Card). Paste a monthly CSV or statement export: it parses into real transaction rows (source='apple_card_paste'), dedups against prior pastes via a deterministic synthetic id, fuzzy-matches the account, and feeds both the onboarding scanner and the statement-estimate rollup. When the paste carries a statement total, the card's statement instance is promoted to that observed amount and a sticky manual balance is recorded. Defaults todry_run=True(parse + preview only); re-run withdry_run=falseto write.
2. MODEL — turn facts into a forecast
Obligations and dated instances are the cash-flow truth. A durable obligation plus its exact dated
obligation_instancesdrive a deterministic, day-by-day cash-flow projection over the requested windows. The projection reads only obligation instances — nothing else can move the forecast.Recurring-charge onboarding discovers candidate recurring charges from transaction history and stages them in a review queue. Candidates are not cash-flow truth: they never write obligation instances and cannot change the forecast until a human accepts and applies them. A candidate classifier triages each discovery into
surface(worth a human look),park(low-signal, held quietly), orauto_reject(clear noise), with safety backstops so anything large, recurring with high confidence, or otherwise material is never silently rejected and always gets surfaced.Reconciliation and drift match expected instances against observed transactions, and flag missing payments, stale estimates, amount changes, and unmodeled recurring charges. Estimates contradicted by a live balance (for example an averaged charge still projecting against a dormant, paid-off card) are auto-expired so stale outflows stop skewing the forecast.
Goals track savings targets and pace; follow-ups are dated reminders the daily routine fires on.
Guardrails carry forward operating rules of thumb as explicit, evidence-backed checks (for example a cash floor: the projected lowest balance must not drop below a threshold).
3. SURFACE — push what needs attention
A daily routine collects everything worth acting on today (matches to confirm, goals behind pace, estimates past review, stale balance-only snapshots, guardrail trips) into one prioritized queue (
get_surface_queue/get_daily_digest).surface_due_items_to_todoistpushes those items to Todoist through an idempotent emissions ledger. Each action-queue item names either its direct task or its membership in thefinance-statusrollup. A bill whose due date moved is re-keyed onto its existing open task rather than getting a second one, so the board never carries the same question twice. Completing a snapshot task acknowledges only that task's evidence hash, so changed evidence resurfaces. A truefollowup:<id>completion still resolves its source follow-up.The surfaced daily run finishes with a conservation check against a fully paginated Todoist read. Every current action must have an open managed task, a dismissal for the current evidence hash, or documented membership in the live
finance-statusrollup. Missing coverage and partial Todoist reads make the run non-green.
Tool catalog
The server registers 90 MCP tools. They group by area as follows. (Names are exact; see src/financial_agent/server.py for signatures.)
Ingest
sync_simplefin: pull accounts, balances, and transactions from SimpleFIN by idempotent upsert (read-only against the feed). A feed row that lands after a checking paste absorbs the pasted copy (same account, posted date, and amount), re-pointing any matches or evidence at the feed row.set_manual_balance: record an authoritative-for-the-day balance snapshot for a slow-refreshing, balance-only feed.import_checking_activity: paste checking-account activity as CSV for the manual-sourced operating account. Parses date/description/amount rows, fuzzy-matches the account, stamps deterministic synthetic ids so a re-paste is idempotent, skips rows the SimpleFIN feed already has (reported asfeed_duplicate; the feed row wins), and writes real transaction rows (source='checking_paste'). Passbalanceto record a sticky manual balance in the same write. Dry-run by default.dedupe_pasted_transactions: one-time cleanup for history imported before the feed-duplicate skip. Pairs eachchecking_pasterow with a feed row on the same account, posted date, and amount, re-points reconciliation matches and evidence at the feed row, and deletes the paste copy. Dry-run by default; reports the pairs and the double-counted total.import_card_statement: the card-statement equivalent (see Statement cycles, below).
Status, projection, and digest
get_finance_status— compact by default: balances, source freshness, projection summaries, guardrail findings,trace_id, and result references. Passcompact=falsefor full per-day event arrays.get_daily_digest— the human-readable morning summary (working cash, multi-window projection, upcoming obligations with running balances, drift/review items, recurring candidates, and a GREEN/YELLOW/RED status), each with provenance. Also includes an obligation coverage summary (how much of what you owe is modeled vs silent autopay vs unmodeled discovered charges) and a trough-sensitivity line that shows how much the projected low point swings on its estimated outflows, so a precise-looking low point is not read as fact. Also carries a read-onlyverificationblock (ok flag plus per-severity finding counts and details) from the deterministic verification phase, so a digest that reads clean while its source rows disagree shows up immediately. When the adversarial reviewer has run, it also carries anadversarial_reviewblock: the persisted advisory flags from an independent reviewer, clearly labeled attention-routing (look here, this looks off), never a verdict. The digest only reads these persisted rows; it never spawns the reviewer.summarize_spending— outflow spending by category / merchant / month with totals, a month-over-month trend, and the transaction ids behind each bucket (rules-based, no LLM).list_transactions: individual transactions, newest first, so an exact charge amount can be quoted instead of only the aggregates fromsummarize_spending. Filters: date range, substring query over payee and description, minimum absolute amount, account, pending. Limit caps at 500 and atruncatedflag signals more rows matched.verify_grounding— the "is the agent allowed to say this number" gate: confirms each headline figure traces to a source row.
Obligations and instances
apply_obligation_instances,delete_obligation_instance,list_obligationsset_obligation_end: set or clear the date a recurring bill stops projecting (a lease, a loan payoff, a subscription being cancelled), so it does not fill the runway forever. Reversible: instances are excluded past the end date, not deleted.deactivate_obligation: retire a whole obligation so all its instances drop out of the projection, listings, reconciliation, and drift. Rows are preserved for audit and it reports how many upcoming bills it pulled from the runway.list_obligation_review_candidates— estimated amounts whose review date has arrived (for example a statement estimate to refresh after close).list_statement_input_estimates— card-spend estimates that feed statement estimates without directly reducing checking cash flow.
Income and calendar
list_income_sources,apply_income_source,generate_income_instancesimport_calendar_facts,list_calendar_facts— normalized pay-date and business-closure facts that drive income scheduling.
Recurring-charge onboarding (discover -> review -> apply)
scan_charge_onboarding_candidates— deterministic background discovery; proposes candidates, never writes canonical obligations.list_charge_onboarding_queue,get_next_charge_onboarding_candidate— work the queue, prioritized by estimated monthly cash impact.record_charge_onboarding_decision—defer/reject/needs_more_evidence/in_review/accept/reset.record_charge_onboarding_decisions: the same decision for many candidates in one call, to clear a large queue without the one-at-a-time grind. Items apply independently inside one transaction, so a bad item is reported as an error instead of aborting the rest.preview_charge_onboarding_apply— read-only preview of what applying would create.apply_charge_onboarding_candidate— guarded write that promotes an accepted candidate into a canonical obligation plus instances (idempotent: re-applying a window updates in place).auto_model_high_confidence_recurring,backfill_recurring_instances
Statement cycles (for card-statement-payment obligations)
aggregate_statement_inputs,list_statement_cycles,recompute_statement_estimates— roll card-input charges into the statement cycle that pays them; never overwrites a confirmed/observed amount.get_statement_status: the latest closed statement plus the open cycle's pace for one card: spend so far, modeled amount, variance, and whether spend is running ahead of or behind the model.set_statement_actual: record an observed statement balance directly (for example a portal-read Apple Card balance) on the matching statement instance, picked by cycle close date or due date. Written as confirmed with provenance, and never overwritten by the rollup estimator.import_card_statement— paste a monthly card statement (CSV or statement text) for a card with no live transaction feed (for example the Apple Card) to fill the balance-only blind spot. Parses into real transaction rows, dedups against prior pastes, fuzzy-matches the account, feeds the onboarding scanner and statement rollup, and (when a total is supplied) promotes the statement instance to that observed amount. Dry-run by default; re-run withdry_run=falseto write.
Reconciliation and drift
reconcile_obligation_instances— match expected instances to observed transactions (conservative by default; never silently marks paid).list_matched_obligation_instances,list_unmatched_obligation_instanceslist_reconciliation_review_items,confirm_reconciliation_match,unconfirm_reconciliation_match— confirming a match marks an instance paid using its recorded transaction match (guarded — never auto-pays).list_check_suggestions,confirm_check_suggestion,reject_check_suggestion: advisory pairings between a posted generic check and the bill it probably paid. Confirming returns the refreshed projection; rejecting is durable and leaves the bill untouched.suppress_contradicted_estimates: lower or retire an averaged estimate that the account's real burn contradicts (for example a card payment still projecting at full size after actual spending on that card collapsed). Compares modeled monthly outflow to observed burn over a lookback window and either rewrites the amount down or routes the obligation to dormant. Both paths are reversible and emit a drift finding.detect_drift,list_drift_findings
Debts
set_debt_terms: create or update a debt's terms (APR, linked account, revolving flag, minimum payment). Re-running with the same id updates in place. Usebalance_source='manual'with an override for debts that have no synced account.list_debts: debts with live balances, modeled monthly interest, autopay and revolving flags, plus total monthly interest across the revolving debts only.
Guardrails
evaluate_guardrails,list_guardrail_findings,apply_guardrail_rules
Goals
set_goal,list_goals,set_goal_override
Follow-ups and the surface queue
capture_followup,list_due_followups,resolve_followupupdate_followup: edit a follow-up in place by id (reschedule, reword, re-prioritize, relink). Re-capturing instead would create a new row, because the capture id is derived from the content.get_surface_queue— the single read for the daily surfacing job.
Todoist output and the action outbox (writes gated OFF by default; Todoist is output-only)
surface_due_items_to_todoist— idempotent push via the emissions ledger. It recordscreate_pendingbefore transport; after an uncertain response it searches for the embedded[fa:<surface_key>]marker and retries only after a complete read proves absence.reconcile_todoist_emission,reconcile_todoist_completions— adopt pre-existing tasks and record the evidence hash acknowledged by a completion. Checkboxes never approve financial reviews; only true follow-ups resolve their source records.reconcile_todoist_project— server-side LIST + classify of the whole Finance project, cleaning drift via a safe three-rule deletion model (ritual/manual tasks are never deleted).list_todoist_project— the read-only counterpart (LIST + classify, no delete path), so the agent's board read goes through the server, never raw HTTP. Each task entry includes itsdue_dateanddescription, so a due-date audit can run through the MCP without touching the raw Todoist API.create_todoist_task,execute_action_outbox,list_action_outbox— create a one-off reminder and process the durable outbox; nothing is sent externally unless write-back is explicitly enabled.update_todoist_task,complete_todoist_task,reopen_todoist_task,delete_todoist_task— edit, close, reopen, or delete an existing task by id for routine board maintenance; gated the same way (no external call unless write-back is enabled).list_today_tasks_all_projects: read-only sweep of tasks due today or overdue across every project, the companion to the Finance-onlylist_todoist_project. It catches finance-relevant tasks filed under Personal or elsewhere, and leaves relevance to the reader rather than hiding them behind a keyword filter.
Verification (deterministic row-tie checks; no LLM)
run_verification— runs the verification phase: four pure-SQL/Python checks that prove the source rows tie together — projection identity (each window's ending balance equals its start plus its signed events), duplicate instances (no two projectable instances share an obligation and due date), statement identity (a cycle's denormalized input_sum/input_count matches its input rows), and instance sign sanity (no projectable instance has a negative stored amount). Persists each finding by default;persist=Falseis read-only.list_verification_findings— read the recorded verification findings (open by default), newest first, optionally filtered bycheck_idor bysource(deterministicfor the pure-code identity checks,adversarialfor the spawned-reviewer's advisory flags).acknowledge_verification_findings: mark known-open findings as seen so later runs report only new ones. Acknowledged findings stay listable and still resolve automatically once the underlying identity is fixed, but stop flipping the verify summary'sok. Without explicit ids it blanket-acknowledges openwarnfindings and deliberately skipserrorseverity.
Adversarial review (non-deterministic; an independent reviewer, advisory only)
run_adversarial_review— spawn the Claude Code CLI (claude -p) as a read-only subprocess on the user's Claude subscription (OAuth; no Anthropic API key) and ask it to try to refute the riskiest part of the forecast: the estimated, low-confidence outflows that land on the projected low point, the large estimated obligations that move the projection, and the freshly-classified recurring-charge candidates with their evidence. Each flag persists intoverification_findingstaggedsource='adversarial'and surfaces alongside the deterministic checks. Findings are ATTENTION-ROUTING ("look here, this looks off"), never verdicts — the reviewer is a language model and can be wrong. Fail-open: a missing CLI, error, timeout, or unparseable reply returnsavailable=Falseand writes nothing, so a broken reviewer never breaks the run. Runs automatically inside the dailyrun_background_synconly when enabled (see Adversarial review enforcement, below); call it directly to review on demand.
Background runner and job health
run_background_sync— orchestrates the whole pipeline (sync -> scan -> reconcile -> detect drift -> suppress dormant estimates -> verify -> surface due items) as one auditable run with an ordered event log; a failing step is recorded and the run continues. Surfaced runs add a final Todoist coverage check after completion reconciliation. Theverifystep persists its findings tagged with the run id. When the adversarial reviewer is enabled, an extraadversarial_reviewstep runs betweenverifyandsurface_due_items; with the gate off the step does not appear and the default sequence is unchanged.get_background_run,list_background_runs,get_job_healthget_version: the version and git commit of the code the server process is actually running. The server is long-running, so code merged to main only takes effect after a restart; these values are captured at startup, which makes a stale live session visible.
Memory (corrections, decisions, facts to recall)
write_finance_memory,search_finance_memory,list_finance_memories,delete_finance_memory— a deterministic, dependency-free bag-of-words embedding with a context-control retrieval policy (similarity threshold, then top-k, then a token budget).
Migration, validation, and parity (one-time bootstrap and cutover)
apply_obligation_migration— seed a fresh DB from legacy files once (not an ongoing input).run_live_validation— prove the pipeline on live data against a throwaway copy without touching the committed snapshot.compare_to_legacy— diff a legacy cash-flow file against the new digest and report differences with a severity each.
Adversarial review: an independent reviewer for the riskiest numbers
The deterministic verification phase proves the model ties out internally — pure code, so a finding is a genuinely broken identity. The adversarial review answers a softer question that code cannot: does the riskiest part of the forecast look wrong to a fresh pair of eyes? It hands an independent reviewer the highest-leverage rows (the estimated, low-confidence outflows sitting on the projected low point; the large estimated obligations that move the forecast; the freshly-classified recurring-charge candidates and their evidence) and asks it to point at whatever looks off.
Honest framing, baked in. An adversarial finding is attention-routing ("look here, this looks off"), never a verdict. The reviewer is a non-deterministic language model: it can be wrong, miss things, or invent concerns. Findings are stored advisory-labeled and a human decides. They never move the projection and never auto-resolve a deterministic check.
Subscription auth, no API key. The real reviewer spawns the Claude Code CLI (claude -p) as a read-only subprocess using your Claude subscription via OAuth. The child environment has ANTHROPIC_API_KEY removed so it can never silently fall back to a metered API key. The subprocess gets no tools and is isolated from this MCP server (so it cannot recurse); every row it judges is embedded inline in the prompt as untrusted text.
Enable it. The phase is off by default and inert offline and in tests. It runs only when both are true: the environment flag FINANCE_AGENT_ADVERSARIAL is truthy (1/true/yes/on) AND the claude binary resolves on PATH. Optional tuning: FINANCE_AGENT_ADVERSARIAL_MODEL (default fable) and FINANCE_AGENT_ADVERSARIAL_TIMEOUT (seconds, default 300).
Three enforcement layers
The same review is reachable three ways, so a material change cannot slip past review regardless of how the work happens:
Daily run (code). When the gate is on,
run_background_syncappends a gatedadversarial_reviewstep betweenverifyandsurface_due_items. This is the scheduled, always-on path.Inside an MCP call (surfaced reads). The
run_adversarial_reviewtool runs the reviewer on demand, andget_daily_digestsurfaces the persisted advisory flags (a pure read — the digest never spawns the reviewer). An agent working in the model can both trigger a review and see prior flags.Outside the MCP call (Claude Code Stop hook). A
Stophook in.claude/settings.jsonrunspython -m financial_agent.adversarial --as-of <today>once when the agent finishes a turn, so no session ends on an un-reviewed material change. It is debounced by nature (one spawn per turn, not per mutation) and short-circuits instantly when the gate is off.PostToolUse-on-mutations is a stricter alternative; Stop-once-per-turn is the cost-sane default.
The module entry point (python -m financial_agent.adversarial --as-of <YYYY-MM-DD> [--db <path>] [--model <model>]) is safe to run with the gate off — it prints adversarial review disabled and exits 0 without spawning anything.
Setup
Prerequisites
uv for dependency and environment management.
Python >= 3.11 (declared in
pyproject.toml).A SimpleFIN access URL for bank balances and transactions (optional; the server runs without it, just with no live ingest).
A Todoist API token if you want task-board sync and surfacing (optional).
Run the server
uv run financial-agent-mcpFINANCE_AGENT_DB_PATH is required — the server repo holds code only and ships no database, so it never reads an in-repo file. Point it at the SQLite DB in your own working directory; the server creates the file (and its parent directory) and the app schema on first use, so a fresh path in a chosen directory bootstraps cleanly. Source tables (accounts, balances, transactions) are created on the first sync_simplefin run.
Register it as an MCP server
Add an entry to your MCP client's config (for Claude Code, the workspace .mcp.json). The server runs over stdio out of this repo via uv, so no install/copy of the code is needed:
{
"mcpServers": {
"financial-agent": {
"command": "uv",
"args": ["run", "--directory", "/path/to/personal-finance-agent", "financial-agent-mcp"],
"env": {
"FINANCE_AGENT_DB_PATH": "/path/to/your/transactions.sqlite",
"FINANCE_AGENT_ENV": "/path/to/your/.env"
}
}
}
}FINANCE_AGENT_DB_PATH(required) points the server at your local SQLite database; it is created with its parent directory if it does not exist.FINANCE_AGENT_ENVpoints at the.envholding your credentials (defaults to~/dev/areas/finances/.env). Setting it lets a registered server read a sandbox.envwithout touching any other workspace.
Credentials (.env)
Credentials are read from the .env file at runtime and are never logged or committed. Create a .env with placeholder values like the block below — fill in your own:
# Bank / balance + transaction feed (SimpleFIN)
SIMPLEFIN_ACCESS_URL=https://USERNAME:PASSWORD@bridge.simplefin.org/simplefin
# Todoist task board (optional)
TODOIST_API_TOKEN=your-todoist-api-token
TODOIST_PROJECT_ID=your-finance-project-id # optional
# Live Todoist write-back is OFF unless this is truthy (1/true/yes/on).
# With it unset, the outbox stays dry-run and makes no external calls.
TODOIST_WRITE_ENABLED=false # optionalThe presence of each credential is surfaced as a safe boolean (has_simplefin, has_todoist) — the secret values themselves are never returned. If TODOIST_PROJECT_ID is unset, the server falls back to a legacy lookup; set it explicitly to drop that fallback.
Running the tests
uv run --extra dev python -m pytestRun pytest as a module (python -m pytest), not the bare pytest console script: the bare script can resolve to a system Python that lacks the mcp dependency and silently skip the MCP-layer wiring tests. Running as a module pins the project venv, so a green run shows 0 skipped.
Privacy
This repository contains no personal financial data. All balances, transactions, obligations, and credentials live in your local SQLite database and your .env file, both of which are gitignored (*.sqlite, *.db, .env, and the data/ directory are all excluded). The server is read-only against the upstream sources and is designed never to mutate the original feed; live ingest pulls into a local copy.
Todoist write-back is OFF by default. Even with credentials present, the action outbox stays dry-run until TODOIST_WRITE_ENABLED is explicitly turned on.
Available Tools
55 toolsaggregate_statement_inputsB
Group card-statement-input charges into the statement cycle that pays them.
Deterministic and idempotent. Builds statement cycles from the target obligation's statement instances (those with a statement close date) and assigns each card input to its cycle, reporting any inputs that fall past the last known statement close as unrolled.
Inputs: target_obligation_id (required), db_path.
apply_charge_onboarding_candidateA
Promote an accepted candidate into a canonical obligation plus dated instances.
This is the guarded write that turns a reviewed candidate into cash-flow truth. By default the candidate must already be accepted (record an accept decision first). Writing is idempotent: re-applying the same window updates instances in place instead of duplicating them.
Inputs: candidate_id (required), start_date, through_date, horizon_days, +3 more.
apply_guardrail_rulesC
Idempotently seed the default guardrail rules into the database.
Inputs: db_path.
apply_income_sourceC
Create or update an income source and schedule version after user confirmation.
Inputs: source (required), db_path.
apply_obligation_instancesC
Create or update an obligation and exact dated instances.
Inputs: obligation (required), instances (required), db_path.
apply_obligation_migrationA
Migrate the complete obligation set from a legacy source into canonical rows.
source is 'obligations_yaml' (trusted, machine-readable) or 'cashflow_md' (stale narrative, imported as needs_review). Instance-level dedup skips anything already modeled; ambiguous rows become needs_review. dry_run (the default) computes the full plan and writes nothing. Reads the legacy file read-only.
Inputs: path (required), source, dry_run, options, +1 more.
auto_model_high_confidence_recurringA
Apply HIGH-confidence, well-evidenced direct-checking recurring candidates (e.g. a car payment) as proper obligations so they enter the cash-flow projection and the runway becomes accurate. Conservative: only confidence=high direct-checking with >=3 occurrences; everything else stays in the review queue.
Inputs: as_of_date, db_path.
backfill_recurring_instancesA
Materialize past-due instances for active recurring obligations over a trailing window and reconcile them against posted transactions, so the digest can answer "did rent / Amex / Apple clear this cycle?". Past instances do not enter the cash-flow projection (forward-only); no payment is fabricated - matches come from the normal reconciliation matcher.
Inputs: as_of_date (required), lookback_days, db_path.
compare_to_legacyA
Parallel-run parity: diff a legacy cash-flow.md against the new daily digest.
Reads the legacy markdown the caller points at (supply a fresh one from your own daily ritual); never runs the legacy ritual or writes any legacy file. Reports matched / missing-in-new / extra-in-new / amount-or-date-changed obligations with a severity each, plus the working-cash delta. Set render_markdown for a short parity summary under the 'markdown' key.
Inputs: legacy_cashflow_md_path (required), as_of_date, base_year, render_markdown, +1 more.
confirm_reconciliation_matchA
Mark a reviewed obligation instance paid using its recorded transaction match. Guarded: requires a recorded match (run reconcile first); never auto-pays. Reversible with unconfirm_reconciliation_match.
Inputs: instance_id (required), db_path.
dedupe_todoist_recurring_duplicatesB
Cancel future instances of a Todoist-imported obligation that duplicates a proper recurring obligation (e.g. a stale one-off "New York Times" $28.62 vs the recurring "New York Times subscription" $30.30). Conservative subset match; reversible (status -> canceled).
Inputs: as_of_date (required), db_path.
delete_finance_memoryB
Delete a finance memory by id (e.g. when a correction is no longer true).
Inputs: memory_id (required), db_path.
detect_driftB
Detect evidence-backed drift findings, ordered by severity.
Finds missing expected obligations (past due, no matching transaction), stale estimates (review date passed), amount changes (charge happened but differs materially), and unexpected recurring charges (discovered by onboarding but not yet modeled). Deterministic and idempotent. When persist is true, findings are upserted and disappeared ones marked resolved.
Inputs: as_of_date (required), options, persist, db_path.
enqueue_todoist_review_batchA
Record the day's review batch in the durable action outbox. No live write.
Idempotent (one batch per day). dry_run (default true) records the payload without intending to send; dry_run=false marks it pending for a sender that is intentionally not configured. Nothing is ever sent to Todoist here.
Inputs: as_of_date (required), options, dry_run, db_path.
evaluate_guardrailsA
Evaluate operating guardrails (cash floor, drift threshold, window age, debt avalanche).
Returns findings ordered by severity. Reads balances and drift from the DB. When persist is true, records the evaluation (pass/fail per rule).
Inputs: as_of_date (required), persist, db_path.
execute_action_outboxA
Process outbox items. Live Todoist sending is gated OFF by default.
Dry-run items are always simulated. Pending items send to Todoist ONLY when TODOIST_WRITE_ENABLED is set in the finances .env AND a token + project id are configured; otherwise they are marked awaiting-integration (no external call). When enabled, sending is idempotent - one task per outbox key, updated in place on rerun rather than duplicated.
Inputs: options, db_path.
generate_income_instancesC
Generate dated income obligation instances from configured income schedules.
extra_closure_dates accepts YYYY-MM-DD dates imported from a payroll or calendar source for one-off local closures.
Inputs: start_date (required), through_date (required), db_path, extra_closure_dates.
get_background_runC
Return a background run record plus its ordered operation-event log.
Inputs: run_id (required), db_path. Returns: result
get_daily_digestB
The daily finance summary (replaces just daily): balances, cash-flow
projection, upcoming obligations, drift/review items, recurring candidates,
and guardrail status, each with provenance. Set render_markdown for a
cash-flow.md-style narrative under the 'markdown' key. Read-only.
Inputs: as_of_date, windows, render_markdown, db_path.
get_finance_statusC
Return read-only finance status: balances, source freshness, trace ids, and stable V1 slots.
Inputs: windows, db_path, working_account_id, start_date.
get_next_charge_onboarding_candidateB
Return the single highest-priority unresolved charge-onboarding candidate.
Inputs: db_path. Returns: result
import_calendar_factsA
Import normalized calendar facts into local storage.
This tool expects source-specific calendar import to happen before the call. Facts should include fact_type, date, source, and optional external_id, calendar_id, related entity, title, confidence, notes, and payload.
Inputs: facts (required), db_path.
import_todoist_obligationsA
Import cashflow-candidate Todoist tasks as canonical one-off obligations.
The local DB stays the source of truth; Todoist is the origin for one-off obligations only. Idempotent by task id. A checked/completed task sets a review date but is never auto-marked paid; a deleted task cancels its instance; a task resembling a recurring obligation is flagged needs_review_dedup_conflict instead of imported. If tasks is omitted, reads cashflow_candidate tasks from the local todoist_tasks snapshot.
Inputs: tasks, options, db_path.
list_action_outboxC
List durable action-outbox items (intended external writes) and their status.
Inputs: status, db_path.
list_background_runsC
List recent background runs with their status and timing.
Inputs: run_type, status, limit, db_path.
list_calendar_factsC
List local normalized calendar facts with optional filters.
Inputs: fact_type, start_date, through_date, status, +3 more.
list_charge_onboarding_queueA
List charge-onboarding candidates ordered by estimated monthly cash impact.
By default returns only the active queue (candidates still awaiting a decision). Pass status to filter exactly, or include_resolved=True to see decided/paused candidates such as deferred or rejected ones.
Inputs: status, limit, include_resolved, db_path.
list_drift_findingsC
List stored drift findings, filtered by status (default active) and type.
Inputs: status, finding_type, db_path.
list_finance_memoriesC
List stored finance memories, optionally filtered by kind.
Inputs: kind, limit, db_path.
list_guardrail_findingsC
List recorded guardrail evaluations, optionally filtered by date or rule.
Inputs: evaluation_date, rule_type, db_path.
list_income_sourcesC
List configured income sources, schedule versions, review dates, and generated horizons.
Inputs: db_path.
list_matched_obligation_instancesC
List obligation instances matched to a transaction, with score and evidence.
Inputs: match_type, db_path.
list_obligation_review_candidatesC
List obligation instances that need review, such as estimated amounts due for refresh.
Inputs: as_of_date (required), db_path.
list_obligationsC
List local canonical obligations and optionally their dated instances.
Inputs: kind, status, include_instances, db_path.
list_reconciliation_review_itemsB
List recorded transaction matches whose obligation instance still awaits confirmation (the day's close-out queue). Read-only.
Inputs: as_of_date, db_path.
list_statement_cyclesC
List statement cycles for a card obligation with their aggregated card-input evidence.
Inputs: target_obligation_id (required), db_path.
list_statement_input_estimatesC
List card-spend inputs that feed future statement estimates without directly reducing checking.
Inputs: target_obligation_id, start_date, through_date, status, +1 more.
list_todoist_sync_recordsC
List Todoist-to-obligation sync records, optionally filtered by status or task id.
Inputs: sync_status, external_task_id, db_path.
list_unmatched_obligation_instancesC
List expected obligation instances with no matching transaction (drift inputs).
Inputs: past_grace_only, db_path.
preview_charge_onboarding_applyA
Preview the canonical obligation and dated instances that applying would create.
Read-only and writes nothing. Use this to show a reviewer exactly what would land in the cash-flow model (obligation, instances, schedule summary, and warnings) before committing to apply.
Inputs: candidate_id (required), start_date, through_date, horizon_days, +2 more.
preview_todoist_review_batchA
Render the day's review items (drift findings) as a Todoist task + subtasks.
Read-only and sends nothing. Returns the parent task and per-item subtasks with specific guidance, so the user can see the review batch before any write.
Inputs: as_of_date (required), options, db_path.
recompute_statement_estimatesA
Fill unconfirmed statement estimates from the card-input rollup, guarded.
Only statement instances whose amount is an unconfirmed projection are recomputed, as baseline (expected non-modeled card spend) plus the rolled-up modeled card inputs for that cycle. Portal/observed amounts are never overwritten. Idempotent.
Inputs: target_obligation_id (required), baseline, db_path.
reconcile_obligation_instancesA
Match expected obligation instances against observed transactions.
Deterministic and idempotent. Records the best transaction match per instance as review evidence (amount + date + merchant scoring). Conservative by default: matches are not silently marked paid (set options.auto_mark_paid to opt in), unmatched past-grace instances become needs_review not overdue (set options.flag_unmatched_needs_review to opt in), and card-statement-input instances are skipped (they settle via the statement, not a checking match).
Inputs: as_of_date (required), options, db_path.
record_charge_onboarding_decisionA
Record a review decision against a charge-onboarding candidate.
Supported actions in this slice: defer, reject, needs_more_evidence, in_review, reset. Applying a candidate into a canonical obligation (accept/apply) or restructuring (merge/split) is a separate guarded slice and is rejected here.
Inputs: candidate_id (required), decision (required), db_path.
resolve_todoist_dedup_conflictC
Resolve a flagged Todoist dedup conflict: import_anyway, skip, or merge.
Inputs: external_task_id (required), decision (required), merge_with_obligation_id, db_path.
run_background_syncB
Run the finance pipeline as one auditable background run.
Orchestrates: scan charge candidates, reconcile transactions, detect drift, and preview the Todoist review batch (dry run). Records a run record plus an ordered operation-event log. A failing step is logged and the run continues (partial_success). Returns the run id, trace id, status, and step summaries.
Inputs: as_of_date (required), options, run_type, trigger_type, +1 more.
run_live_validationA
Validate the pipeline on live data without touching the committed snapshot.
Copies the database to a throwaway working DB, optionally pulls live SimpleFIN
Todoist into the copy, then runs the read pipeline (onboarding scan, reconciliation, drift, guardrails) and returns a report with integrity checks (e.g. no orphaned statement targets after a card rename). The source database is never mutated.
Inputs: as_of_date (required), sync, keep_work_db, db_path.
scan_charge_onboarding_candidatesA
Scan transaction history and discover reviewable charge-pattern candidates.
This is the background-discovery entry point: it groups related transactions by merchant and account, proposes schedule/amount/cash-impact/review policies, and stores durable candidates in a review queue. It is idempotent and never writes canonical obligations or moves cash flow. Options support min_evidence, include_inflows, and link_existing_obligations.
Inputs: options, db_path.
search_finance_memoryA
Retrieve the most relevant finance memories for a query, under a context policy.
Records are scored by similarity, then filtered by min_score, capped at k, and bounded by a max_tokens budget. The result reports how many records each limit dropped so the amount of memory entering context is explicit.
Inputs: query (required), k, min_score, max_tokens, +2 more.
summarize_spendingA
Summarize outflow spending over a date range, grouped by category, merchant, or month: totals, counts, top buckets, a month-over-month trend, and the transaction ids behind each bucket. Transfers and income are excluded by default. Read-only. Set render_markdown for a summary under 'markdown'.
Inputs: start_date (required), end_date (required), group_by, exclude_transfers, +2 more.
sync_simplefinA
Pull live accounts, balances, and transactions from SimpleFIN into the DB.
Reads SIMPLEFIN_ACCESS_URL from the finances .env at runtime (never returned). Read-only against SimpleFIN; idempotent upsert by transaction id. When start_date is omitted, incremental=true resumes from the last synced transactions (cheap for a daily run); otherwise it pulls lookback_days (SimpleFIN caps the window at 90 days). Returns counts and any warnings.
Inputs: start_date, end_date, lookback_days, incremental, +1 more.
sync_todoistA
Pull the live Todoist board (tasks + sections) into the local DB, read-only.
Reads TODOIST_API_TOKEN and the project id from the finances .env / obligations.yaml at runtime (never returned). Normalizes each task into the cashflow fields the onboarding importer uses, upserts by id, and marks tasks no longer seen as deleted. Does NOT write to Todoist.
Inputs: db_path.
unconfirm_reconciliation_matchB
Reverse a confirmation: return the obligation instance to 'expected' and clear the matched-transaction evidence.
Inputs: instance_id (required), db_path.
verify_groundingA
Check that every headline dollar figure in a finance payload (a get_finance_status or get_daily_digest result) traces to a source: working cash to the latest operating-account balance snapshot, each upcoming obligation to its instance row, each projection endpoint recomputable from those. Returns a grounding report flagging any number it could not trace. Read-only - use it to verify a finance answer before relying on it.
Inputs: payload (required), as_of_date, db_path.
write_finance_memoryA
Store a finance memory (a correction, decision, or fact to recall later).
Idempotent by (kind, source, text). Use kind to scope memories, e.g. 'decision', 'correction', 'fact'.
Inputs: text (required), metadata, kind, source, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 55 tools
Tools are largely distinct with clear purposes, but the high number (55) may cause some confusion. For example, multiple 'apply_*' and 'list_*' tools are differentiated by their target entities, and detailed descriptions help. A few tools like 'run_background_sync' and 'run_live_validation' have overlapping scopes but are still distinguishable.
All tool names follow a consistent verb_noun pattern using snake_case (e.g., 'apply_obligation_instances', 'list_drift_findings', 'sync_simplefin'). The pattern is predictable and helps agents infer functionality. No mixed styles or vague verbs are present.
With 55 tools, the server is significantly over the typical well-scoped range (3-15). While each tool addresses a specific need in finance management, this large surface area can overwhelm agents and increase selection errors. The domain could likely be consolidated without losing coverage.
The tool set covers a broad lifecycle for obligations, income, reconciliation, drift detection, guardrails, and syncing. Key operations like creation, reading, updating, and deletion are present for most entities, though some CRUD gaps exist (e.g., no explicit delete for obligations). The inclusion of 'verify_grounding' shows attention to integrity.
Maintenance
Related MCP Connectors
Personal finance ledger for AI agents — query spending, track bills, forecast cash flow.
Personal finance for AI agents — onboard, import statements, categorize & budget over MCP.
Personal finance for your AI agent: money goals, a budget, and a review of your accounts.
Personal finance for AI agents: accounts, budgets, goals, 9-strategy debt payoff, reports. OAuth 2.1
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceTurns a personal-finance SQLite database into typed, schema-validated tools that an AI assistant can call directly, letting you manage accounts, transactions, budgets, debts, investments, tax estimates, and goals through natural language.66 npmMIT
- AlicenseBqualityCmaintenanceEnables an agent to query local financial ledgers for expenses, investments, and income, with tools for savings rate, accounts, sync, and reconciliation.18MIT
- FlicenseNot gradedqualityBmaintenanceEnables personal finance management through natural language, including tracking income/expenses, categories, loans, subscriptions, budgets, and AI-powered insights, all backed by a local SQLite database.1-
- AlicenseNot gradedqualityBmaintenanceEnables 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.27 npmMIT