mech.app
Financial

Fungible's Terminal Finance Stack: How a Local-First Personal Finance App Handles MCP Integration Without Cloud Sync

A terminal-based finance app exposes double-entry ledger state to MCP agents through filesystem boundaries, no server required.

Source: github.com
Fungible's Terminal Finance Stack: How a Local-First Personal Finance App Handles MCP Integration Without Cloud Sync

Fungible is a terminal-based personal finance app that exposes its double-entry ledger to MCP-compatible agents without running a web server or syncing to the cloud. The entire stack lives on your filesystem. The interesting constraint: how do you let an AI agent query balances, categorize transactions, and generate reports while preventing it from corrupting accounting state?

The answer involves treating the ledger as an append-only log, exposing read-only views through MCP tools, and using filesystem locks for write coordination. No HTTP endpoints. No database server. Just structured files and a protocol boundary.

Architecture: Filesystem as State Store

Fungible stores all financial data in a local directory structure:

  • ledger.json: Append-only transaction log (double-entry format)
  • accounts.json: Chart of accounts with metadata
  • rules.json: Categorization rules for transaction matching
  • imports/: CSV files from bank downloads or Plaid sync

The MCP server runs as a separate process that reads these files. It never writes directly to the ledger. Instead, it exposes tools that return structured data or generate proposed transactions for human approval.

MCP Tool Boundaries

The MCP integration defines four tool categories:

Read-only queries:

  • get_balance: Returns current balance for an account
  • list_transactions: Filters transactions by date, account, or category
  • get_net_worth: Calculates assets minus liabilities

Analysis tools:

  • categorize_transactions: Suggests categories for uncategorized entries
  • generate_budget_report: Aggregates spending by category and time period
  • find_duplicates: Detects potential duplicate transactions

Proposal generators:

  • propose_transaction: Returns a JSON transaction object for review
  • suggest_rule: Generates a categorization rule based on transaction patterns

Import helpers:

  • parse_csv: Converts bank CSV to ledger format (does not write)

The key design choice: no tool directly modifies the ledger. Every write operation returns a proposal that the user must approve in the terminal UI.

Double-Entry Integrity Without Database Constraints

Personal finance apps need to maintain double-entry bookkeeping rules:

  1. Every transaction has equal debits and credits
  2. Account balances must reconcile
  3. Historical transactions are immutable

Fungible enforces these rules at the application layer, not in a database. The ledger file is append-only. Corrections happen by adding reversing entries, not by editing existing records.

When an MCP agent proposes a transaction, the terminal UI validates it before appending:

def validate_transaction(txn):
    # Check double-entry balance
    debits = sum(entry['amount'] for entry in txn['entries'] if entry['type'] == 'debit')
    credits = sum(entry['amount'] for entry in txn['entries'] if entry['type'] == 'credit')
    
    if debits != credits:
        raise ValidationError(f"Unbalanced transaction: {debits} != {credits}")
    
    # Check account existence
    for entry in txn['entries']:
        if entry['account'] not in accounts:
            raise ValidationError(f"Unknown account: {entry['account']}")
    
    # Append to ledger
    with FileLock('ledger.lock'):
        ledger = read_ledger()
        ledger['transactions'].append(txn)
        write_ledger(ledger)

The filesystem lock prevents concurrent writes from the terminal UI and any background import processes. The MCP server never acquires this lock because it never writes.

Concurrent Access Pattern

The terminal UI and MCP server can run simultaneously. The user might be reviewing transactions in the TUI while an agent analyzes spending patterns. This creates a read-write coordination problem.

Fungible solves it with optimistic reads and write serialization:

  • MCP server: Reads ledger files without locks. If data changes between reads, the worst case is stale analysis. The user will see fresh data in the UI.
  • Terminal UI: Acquires a write lock only when appending transactions. Reads are lock-free.
  • Import processes: Acquire the same write lock when adding bulk transactions from CSV or Plaid sync.

This works because financial data has natural append-only semantics. You never delete a transaction. You add a correction. The ledger grows monotonically.

MCP Integration Without a Server Process

Most MCP integrations assume a long-running server. Fungible takes a different approach: the MCP server is a CLI tool that responds to a single request and exits.

# Agent calls this for each tool invocation
fungible mcp-tool get_balance --account "checking"

The MCP client (Claude Desktop, Cline, or another agent framework) spawns the process, reads the JSON response, and terminates it. No persistent connection. No state held in memory between requests.

This design has trade-offs:

ApproachLatencyMemoryComplexity
Long-running serverLow (in-memory cache)High (persistent process)High (connection management)
CLI per requestMedium (file I/O each time)Low (process exits)Low (stateless)

For a personal finance app, the CLI approach works. Ledger files are small (under 1MB for years of transactions). Reading from disk takes milliseconds. The user is not waiting for sub-second responses.

Plaid Sync in a Local-First World

Fungible supports Plaid for automatic bank transaction sync, but it does not send your ledger to the cloud. Instead:

  1. Plaid API calls happen from your machine
  2. Transactions download to imports/plaid/
  3. The terminal UI shows a review screen
  4. You approve or reject each transaction
  5. Approved transactions append to the local ledger

The MCP server can read from imports/plaid/ to help categorize new transactions based on past patterns, but it cannot trigger a Plaid sync. That requires explicit user action in the terminal UI.

This keeps your financial data local while still allowing automated import. The Plaid access token lives in a local config file, not on a remote server.

Observability: Logs and Audit Trail

Every MCP tool call logs to ~/.fungible/mcp.log:

2026-05-26T10:15:32Z [get_balance] account=checking result=2847.32
2026-05-26T10:15:45Z [categorize_transactions] count=12 suggestions=8
2026-05-26T10:16:01Z [propose_transaction] amount=45.00 category=groceries status=pending

The ledger itself acts as an audit trail. Each transaction includes:

  • Timestamp
  • Source (manual, CSV import, Plaid sync, MCP proposal)
  • User approval status

If an agent proposes a bad transaction, you can trace it back through the logs and see exactly what tool call generated it.

Failure Modes

Corrupted ledger file: The append-only design limits damage. If the JSON file becomes malformed, you lose only the last transaction. The terminal UI validates JSON on every write. A backup script can snapshot the ledger daily.

Agent proposes unbalanced transaction: The validation layer rejects it before it touches the ledger. The agent sees an error response and can retry with a corrected proposal.

Concurrent write collision: The filesystem lock serializes writes. If the terminal UI and an import process both try to write, one blocks until the other finishes. No lost transactions.

Plaid token expires: The terminal UI shows an error and prompts for re-authentication. The MCP server cannot trigger this flow. It will see stale data in imports/plaid/ until the user manually refreshes.

Agent hallucinates account names: The validation layer checks account existence. If an agent references “credit_card” but the chart of accounts only has “credit-card-visa”, the transaction is rejected.

Technical Verdict

Use Fungible’s approach when:

  • You want full control over your financial data with no cloud dependency
  • You prefer terminal UIs and can tolerate manual approval steps
  • You need agent assistance for categorization and reporting, not autonomous trading
  • Your transaction volume is low enough that file I/O latency is acceptable (under 10,000 transactions)

Avoid this pattern when:

  • You need real-time multi-device sync (the filesystem is the source of truth)
  • You want agents to execute transactions without human approval
  • You have high transaction volume that would make file parsing slow
  • You need complex queries that would benefit from SQL indexes

The local-first MCP integration works because personal finance has natural boundaries. Transactions are append-only. Balances derive from history. Agents assist but do not decide. The filesystem provides enough coordination for a single-user app.