A local-first personal finance MCP server that connects to real bank accounts via Plaid, stores transactions in SQLite, and provides tools for financial analysis and transaction querying.
Fino provides 12 financial management tools with generally good naming and complete input schemas. Tool names follow verb_noun patterns (sync_transactions, get_accounts, search_learnings). Descriptions are present and moderately detailed (average ~150 chars), though some lack action context. Parameter schemas are well-typed with Zod validation visible in code. However, output schemas are not formally documented in the tool definitions, responses are described in natural language only. Error handling is minimal in the tool definitions themselves; syncing has some retry logic but no recovery guidance for LLM. The server correctly structures parameters with descriptions and types, but lacks several production patterns: no tool annotations (readOnlyHint, destructiveHint, idempotentHint), no output schema documentation, limited error categorization, and no confirmation/dry-run for destructive operations.
Delete a learning record by ID.
Get all connected bank accounts with current balances, types, and institution details
Get a summary of balances across all accounts: total cash, credit used/available, investments, loans, and net worth
Retrieve a specific learning record by ID (obtained from search_learnings). Returns the full content and metadata.
Compare income vs spending month-over-month. Returns months with income, spending, and net with percentage changes.
Get total spending by category over a date range. Returns spending breakdown with percentages.
Query transactions with filtering by date range, category, merchant, or amount. Optionally search by transaction name. Returns CSV format with auto-generated spending summary.
Output schemas not formally documented. Tool descriptions state 'Returns CSV format with auto-generated spending summary' (get_transactions) and 'Returns CSV format' (get_spending_by_category) but actual response structure is not provided in tool definition. LLMs cannot plan downstream calls without knowing what fields to expect.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint). Critical for agent safety: delete_learning and mark_learning_stale have no destructiveHint marker; sync_transactions (WRITE risk) has no annotation; get_* tools lack readOnlyHint. LLMs cannot distinguish safe reads from state-mutating operations without explicit hints.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 62 | 2026-07-28+ | v2 |
Mark a learning as stale/outdated (without deleting it) so Claude knows the information needs validation.
Store a new financial learning (insight, pattern, or goal). Always search_learnings first to avoid duplicates. Learnings accumulate over time and persist across conversations.
Search persistent financial learnings (income profile, budget targets, spending patterns, rules, goals). Returns matching learning records with IDs for use with get_learning. Always call this before get_learning to avoid duplicates.
Force sync all connected bank accounts with Plaid to get latest transactions and balances
Update an existing learning record. Searches first, then updates if found.
Destructive operations lack confirmation/dry-run pattern. delete_learning and update_learning irreversibly modify persistent learnings with no confirmation step. No dry_run parameter or separate confirm_delete tool. Agents make mistakes, a single misplaced call deletes user data.
Limited error handling and recovery guidance. Tool definitions provide no error categorization (retryable vs user-fixable vs fatal). When sync_transactions fails (e.g., Plaid connection expired), LLM receives 'sync failed (error message)' with no hint about whether to retry, ask user, or move on. Code shows Plaid error extraction but no structured error classification.
Description brevity for several read tools. get_accounts (60 chars), get_balances (71 chars), mark_learning_stale (69 chars) fall below the 100-char minimum for unambiguous intent. 'Get all connected bank accounts' does not clarify whether it includes hidden/archived accounts or what balance type is returned.
Natural-identifier support unclear for banking operations. get_accounts and sync_transactions do not document whether account selection by account_id is the only method or if human-friendly account names/nicknames are accepted. Financial users often refer to 'my savings account' or 'checking', not opaque IDs.
get_transactions accepts min_amount and max_amount as separate params with type 'number', but no documented range constraints (e.g., must be positive, max value). Code does not show validation in the visible portion. LLMs can hallucinate negative amounts, zero, or billion-dollar limits.
Pagination not documented for list/query operations. get_transactions has limit (default 50) and offset (default 0) parameters but description does not state result count, whether total is returned, or cursor availability. Agents cannot determine if results are complete or need a follow-up paginated call.
Transactional safety unclear. save_learning, update_learning, and delete_learning lack idempotency guarantees. If save_learning is called twice with the same content, does it create duplicate records or upsert? Agents retry on ambiguous failures, non-idempotent tools risk duplicate data.