TL;DR#
A self-hosted personal finance app built by reverse-engineering Copilot Money into a 120Hz SwiftUI client, a Dockerized Python and SQLite GraphQL backend, and a 22-tool MCP server for local AI.
Decompiling Copilot Money to own my financial data#
I used Copilot Money for my daily budgeting and liked its interface, but hated that my bank history sat in a closed cloud silo behind an annual subscription with no API to build my own tools on top of it.
I wanted that same fast iOS and Mac experience backed by a plain SQLite database on my own machine so I could write custom bank sync scripts and hook up a local AI agent.
To build it, I decompiled the official macOS app (Copilot.app), pulled out its original font files (Averta and MatterVF) and local container state (~/Library/Group Containers/group.com.copilot.production), and rebuilt the entire client natively in SwiftUI for iOS, iPadOS, and Mac Catalyst as Sauce Financial.
Sauce Financial app icon for iOS and macOS.

The flame mark used in the app header and launch screen.
Keeping 120Hz scrolling smooth and hiding real balances in demo mode#
Rebuilding the client in SwiftUI 5.10 with a dark #070A0F background, Averta and MatterVF type, and monthly rollover budget rings exposed two practical issues right away.
Fixing frame drops across years of transactions
When I loaded my multi-year SQLite history into a standard SwiftUI list, scrolling on a 120Hz ProMotion screen stuttered badly whenever cells formatted dates and currency strings on the main thread. I fixed the hitching by precomputing formatted view models on a background queue and building custom cell recycling inside a LazyVStack. That kept scrolling locked at 120 FPS across thousands of rows.
Adding a one-tap demo mode for screen sharing
Because finance apps put real account numbers and net worth on almost every screen, I couldn't record UI demos or test layout changes around other people without showing my personal balances. I added a Demo Mode toggle in settings that swaps my live accounts, transactions, and net worth charts with a realistic fake dataset in one tap.
A wire-compatible GraphQL server, Docker, and 22 MCP tools#
To make the backend durable, I wrote a zero-dependency Python 3 server (backend/app.py and graphql_engine.py) backed by SQLite (copilot.db) that replicates Copilot's exact Relay-style GraphQL queries and mutations (Transactions, Accounts, Categories, Networth, Recurrings, Holdings, EditTransaction, and EditBudget). For live bank feeds, I wired up direct Plaid and SimpleFIN sync services (plaid_service.py and simplefin_service.py).
To reach the server from my iPhone away from home, I packaged the stack in Docker (Dockerfile and a multi-tenant docker-compose.yml so friends or family can run isolated containers on the same host) and exposed it over HTTPS via Tailscale Funnel.
Once all my data lived in copilot.db, I built a 22-tool Model Context Protocol (MCP) server (mcp-server/server.py) with tools like get_accounts, get_transactions, get_budgets, get_cash_flow, edit_transaction, bulk_recategorize_transactions, and sync_amazon_orders. I connected those 22 MCP tools to a locally hosted AI instance through agent_service.py (exposing /api/chat and an OpenAI-compatible /v1/chat/completions endpoint) and built a native chat sheet (SauceChatView.swift) right inside the app.
Matching vague Amazon charges to real items#
The biggest annoyance in my monthly budget was seeing a $142.50 credit card charge labeled AMZN Mktp US. A single Amazon box might hold paper towels, motor oil, and a birthday gift, so lumping the whole charge into one shopping category threw off my monthly budgets.
Matching bank charges to Amazon receipts was trickier than I expected. Amazon rarely bills your card the day you click buy; they charge when items ship, and an order often splits into two or three card charges days apart. Exact date and total matching missed almost half my orders. I wrote amazon_service.py to pull my Amazon order history and check pending card debits across a 3-day shipping window. When it finds a match, it replaces the AMZN Mktp US row with the product names, quantities, unit prices, and thumbnails, then assigns each item to its budget category.
Auditing my budget with a local AI model in under 50 milliseconds#
Because the Python server and SQLite database run on my Mac, opening the app, switching tabs, and filtering years of transactions finish in under 50 milliseconds.
Having the 22 MCP tools wired to my local model in SauceChatView also changed how I clean up my budget. Instead of tapping through dozens of rows by hand, I can ask the chat sheet to audit which recurring subscriptions went up in price this year, check monthly cash flow, or bulk-recategorize transactions without sending my bank statements to a third-party cloud LLM.
Screenshots#
Sauce Financial app icon.

The Sauce flame mark.