Data Sovereignty, AI Agents, and Why Technical Docs Are the New APIs

personal-finance mcp sqlite sovereignty artificial-intelligence open-source

What I want is simple: I want to chat with my family’s personal finances, ask real questions, get real answers, and keep total control over where that data goes. I also don’t want to throw away the decade-plus of careful history I’ve built up in Quicken For Mac.

Quicken is a good app. It’s held my family’s ledger for years without losing a transaction. What I want now is for my own AI assistant to be able to read it.

Last week (May 15), OpenAI launched Personal Finance in ChatGPT, powered by Plaid. I tried it. I linked some accounts. It’s genuinely well-done, and for a lot of people it’s going to be the right answer.

But there were two things I wanted that it couldn’t do. The first: import the decade-plus of Quicken history I already had — categories, payees, splits, tags, investment tax lots, all of it. Plaid’s view starts the day you connect, and it sees what the bank shows it. My Quicken file remembers more. The second: keep the data local. I’d rather not mirror every payroll deposit, tuition payment, medical co-pay, and brokerage trade into another cloud if I don’t have to.

So there’s a third path worth carving out. Local-first data, queried by local-first AI. No aggregator in the middle, no transactions shipped anywhere, and crucially, no need to walk away from years of history that’s already sitting on my own Mac.

So I opened up my Quicken For Mac database to see what was possible. While the app is running, the database sits unlocked on disk as a plain SQLite file under ~/Documents/*.quicken/data. I audited what’s inside. What I found wasn’t just a path to a private personal finance agent; it changed how I think about building software for AI agents in general.

Short version: I no longer think MCP servers are the right shape for most integrations. Skills are. Documentation beats APIs.

What’s actually inside

84 tables. Core Data SQLite, the same format any Mac app using Apple’s persistence stack produces. Account balances, transactions, splits, investment tax lots, security price history. All relational, all sitting on your hard drive.

The standard move here is to build an MCP server. Write a few hundred lines of TypeScript wrapping eight or twelve queries as typed JSON-RPC endpoints. getTransactions(). spendingByCategory(). The usual.

I did exactly this. It works. But it’s the wrong shape.

The engineering side is annoying enough on its own. Native SQLite Node modules (better-sqlite3) throw NODE_MODULE_VERSION mismatches that confuse anyone who isn’t already a Node developer. Every Claude Desktop point release seems to break the install for someone.

The deeper problem is philosophical. When you design an API, you’re declaring: here are the twelve questions I think you’ll ask of this data. The agent gets a sandbox. Nothing outside the sandbox exists.

Skills beat MCP

Modern LLMs don’t need spoon-feeding. Give Claude sqlite3 and a schema doc that actually explains things (Core Data’s 2001 epoch offset, the dynamic Z_ENT lookup for tag entities, the Z_15USERTAGS many-to-many join) and it writes its own SQL. Better SQL than I’d write for an API wrapper, because it composes queries I never thought of.

So I rewrote the integration as a Skill. A single Markdown file, SKILL.md, that teaches the agent:

  1. Where the database lives, and how to open it read-only.
  2. How Core Data lays out entities, dates, and tag relationships.
  3. Which queries to avoid: don’t double-count splits, watch out for the runtime Z_ENT resolution, don’t trust the static category IDs.

Then I asked Claude: “Compare my organic grocery spending in 2024 vs 2025.” It read the schema, found the right categories, joined ZTRANSACTION, ZCASHFLOWTRANSACTIONENTRY, and ZTAG, and answered. No endpoint existed for that question. No endpoint needed to.

Documentation is the API now

If you believe agents are going to keep getting smarter, and you build today’s integrations as narrow APIs, you’re putting training wheels on a spaceship. You’re constraining a 2027 agent to a 2025 developer’s imagination of what someone might want to know.

Raw data plus good docs gives the agent room to be smart. That’s the whole argument.

The job stops being “imagine every query a user might ask” and becomes “leave an excellent paper trail.” Schema docs (mine is here), data dictionaries, query recipes, foreign key maps. The agent does the rest.

The sovereign exporter

To put this in practice, the 1.4.0 release ships export_sovereign_csv.py. Zero dependencies, pure Python. Point it at your unlocked Quicken file and you get back a folder:

  • accounts.csv, categories.csv, payees.csv, transactions.csv, transaction_splits.csv, holdings.csv (with reconstructed tax lots and unrealized returns), and tags.csv. ISO 8601 dates, relational splits.
  • schema.json: a machine-readable data dictionary with types, descriptions, and foreign keys for every exported column.
  • README.md: how to load the whole thing into DuckDB, Pandas, or Postgres in about three lines.

A few seconds, and a multi-decade financial history is portable to any agent on any platform, with no aggregator and no cloud hop in the middle.

Where this still needs work

One honest caveat: the weakest link in this whole stack isn’t the schema, it’s the categorization sitting on top of it. Once you start querying the database directly, the cracks are easy to spot in my own file.

Quicken’s default taxonomy has shifted over the years, and an old Mint import I did at some point didn’t help. My active categories include both Dining (parented under Food) and Restaurants (parented under Food & Dining), each with thousands of splits. Fuel and Gas & Fuel have the same overlap, sitting under Auto and Auto & Transport respectively. A handful of common categories — Coffee Shops, Restaurants, Fast Food, Gas & Fuel — actually exist as two distinct rows in the tag table, one parented and one orphaned, and both still get used. I also have an active category literally named Business Expenses (old) collecting fresh splits; nobody flagged it for cleanup, so I never circled back.

Then there are the catch-alls. Four of my top-ten most-used categories (Shopping, Household, Kid, Uncategorized) sit at the top level with no parent hierarchy at all. Uncategorized alone holds roughly four percent of every split in my file, spread across more than a thousand distinct payees. Ask the agent “how much did I spend on the kids last year” and the answer depends on whether you trust the Kid bucket, the Shopping bucket, or both.

The payee side has its own drift. Roughly seven percent of my payees appear in two or more categories over their history, and a handful have landed in six or more. Some of that is genuinely mixed-basket merchants (a warehouse store, an online retailer that sells everything) but a lot is just me being inconsistent across years. Quicken does track a ZMACHINEGENERATEDCATEGORYSOURCE flag on transactions, which could help separate guesses from confirmations, but it’s set on only about one percent of mine. Everything else looks user-confirmed in the schema even when it was, in practice, autopilot-accepted from a payee rule.

None of this is a critique of Quicken. The same problems would show up in any ledger a real human kept for a decade. It’s just that the moment you point an LLM at the data, the rough edges become load-bearing in a way they weren’t when a human was eyeballing reports.

There’s open work here I haven’t shipped. The shape I’m imagining: an agent-driven reclassification pass that proposes better categories per transaction using payee, amount, timing, and any memo text; a category-merge step for the obvious duplicates above; and a local override layer that stores those corrections in a sidecar SQLite file alongside the sovereign export. Read-only access to Quicken’s database stays read-only — writing into a live Core Data file that Quicken thinks it owns is a reliable way to corrupt it. That does mean improvements an agent makes won’t flow back into Quicken’s UI, and I don’t have a clean answer for closing that loop yet. If you’ve worked on local-LLM transaction classification or have a take on the override-layer design, I’d genuinely like to compare notes.


Try it

The repo is at github.com/dweekly/quicken-mac-mcp. Pick the install path that matches the agent you actually use.

One prerequisite for all paths: Quicken For Mac has to be running. Quicken encrypts the database when it closes; the data is only readable while the app is open.

The plugin bundles the Skill (the smart path) and the MCP server (the typed-tool fallback):

claude plugin install quicken-mac-mcp

Then just ask Claude something like “what did I spend on groceries last month?” The /quicken skill loads automatically and the MCP tools are there as shortcuts.

Claude Desktop

Download quicken-mac-mcp.mcpb from the latest GitHub release and drag it into Claude Desktop. It’ll auto-detect your database (or prompt for a path).

For manual config, edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "quicken": {
      "command": "npx",
      "args": ["-y", "quicken-mac-mcp"]
    }
  }
}

Restart Claude Desktop. You should see eight tools on the hammer icon.

OpenAI (ChatGPT, Custom GPTs, Assistants API)

ChatGPT doesn’t speak MCP, but it speaks CSV fluently. Run the sovereign exporter and upload the result:

git clone https://github.com/dweekly/quicken-mac-mcp.git
cd quicken-mac-mcp
python3 scripts/export_sovereign_csv.py

Then either drag the contents of quicken_sovereign_export/ into a ChatGPT conversation, attach the CSVs as knowledge files in a Custom GPT, or upload them via the Assistants API and turn on file_search. The bundled schema.json and README.md are exactly the “paper trail” the model needs to write its own analyses against your data.

Gemini (Gemini CLI / Code Assist)

Gemini CLI supports MCP servers via ~/.gemini/settings.json:

{
  "mcpServers": {
    "quicken": {
      "command": "npx",
      "args": ["-y", "quicken-mac-mcp"]
    }
  }
}

For Gemini in AI Studio or via the Generative AI API, use the CSV path: run the sovereign exporter, then attach the folder as context. Gemini’s long-context window handles the whole package comfortably.

OpenClaw (and other MCP-capable clients)

Any client that loads MCP servers can use the same config:

{
  "mcpServers": {
    "quicken": {
      "command": "npx",
      "args": ["-y", "quicken-mac-mcp"]
    }
  }
}

If your client also supports Claude-style skills, point it at plugin/skills/quicken/SKILL.md directly. The skill is the smarter path; the MCP server is the safer fallback.


If you try it and something breaks, open an issue. And if you’ve got a Quicken database with a quirk I haven’t seen (multiple data files, an old Mint import, strange investment account types) I’d love an anonymized sample to harden the schema docs against.