Copilot Money MCP
# Copilot Money MCP Server
> Query and manage your personal finances with AI using local Copilot Money data
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
[](https://www.typescriptlang.org/)
[](https://github.com/ignaciohermosillacornejo/copilot-money-mcp/actions/workflows/test.yml)
[](https://codecov.io/gh/ignaciohermosillacornejo/copilot-money-mcp)
[](https://glama.ai/mcp/servers/ignaciohermosillacornejo/copilot-money-mcp)
[](https://registry.modelcontextprotocol.io/?q=copilot-money-mcp)
## Disclaimer
**This is an independent, community-driven project and is not affiliated with, endorsed by, or associated with Copilot Money or its parent company in any way.** This tool was created by an independent developer to enable AI-powered queries of locally cached data. "Copilot Money" is a trademark of its respective owner.
> [!NOTE]
> **Copilot Money has announced an official MCP server (currently in waitlist, read-only).** If a first-party, read-only integration suits your needs, you should strongly consider using it instead of this community project. Learn more and join the waitlist at [agent.copilot.money](https://agent.copilot.money/#mcp).
>
> This project remains useful if you need write tools (categorize transactions, manage budgets, edit recurrings, etc.), fully offline cache-mode reads with zero network requests, or simply want access today without waiting for the official rollout.
> [!WARNING]
> **The official MCP beta currently does not surface Plaid-connected accounts.** Copilot's team has [confirmed the limitation](https://www.reddit.com/r/copilotmoney/comments/1ueh4lv/) and says they're working on a path forward, but there's no timeline. If most of your institutions connect through Plaid, the official MCP will give you an incomplete picture of your finances. This community server is not affected: it reads the app's full local cache (and, in live mode, the same GraphQL API the app uses), so Plaid-connected accounts are included.
## Overview
An [MCP](https://modelcontextprotocol.io/) server that gives AI assistants access to your Copilot Money personal finance data. It reads from the locally cached Firestore database (LevelDB + Protocol Buffers) on your Mac. **Reads are 100% local with zero network requests.**
**14 cache-mode read tools (or 25 in `--live-reads` mode: 8 surviving cache + 17 live), plus up to 19 write tools** — query and modify transactions, accounts, holdings, balances, categories, recurring charges, budgets, goals, and investment performance. See [Tools by Mode](#tools-by-mode) below.
> Contributors: writes go through an API we don't own. How the repo keeps its model of Copilot's GraphQL surface from silently drifting — the conformance ledger, live smokes, and weekly drift check — is documented in [`docs/CONFORMANCE_ARCHITECTURE.md`](docs/CONFORMANCE_ARCHITECTURE.md).
## Privacy First
We never collect, store, or transmit your data to any server operated by this project — we don't have any. See our [Privacy Policy](PRIVACY.md) for details.
- No analytics, telemetry, or tracking of any kind
- Reads are fully local — zero network requests
- Open source — verify the code yourself
> [!IMPORTANT]
> **Heads up about AI providers.** While this server itself runs locally and never sends your data to any server operated by this project, **the AI assistant you connect it to (Claude, ChatGPT, Gemini, etc.) will see your Copilot Money data** as part of answering your questions. That means your financial data will be transmitted to and processed by the provider of whichever model you choose — **Anthropic, OpenAI, Google, or another third party** — subject to that provider's own privacy policy and data retention terms.
>
> **By using this MCP server with a hosted AI model, you are knowingly sharing your financial data with that AI provider.** Only use this tool if you are comfortable with that trade-off. If you are not, consider waiting for an official Copilot Money integration or using a fully local model.
## Tools by Mode
This server exposes different tools depending on which CLI flags you enable.
| Mode | Flag | What it does | Auth | Network | Tools available |
|---|---|---|---|---|---|
| 🟢 Default | _(none)_ | Reads from your local LevelDB cache | ❌ None | 🔌 Zero (offline) | 14 cache-mode read + utility tools |
| 🌐 Live reads | `--live-reads` | Real-time reads via Copilot's GraphQL API; swaps out 6 cache tools and adds 11 live-only ones | 🔒 Browser session | 🌐 HTTPS per request | 25 read tools (8 cache + 17 live) |
| ✍️ Writes | `--write` | Adds mutation tools (transactions, tags, categories, budgets, recurrings, splits) **and turns on `--live-reads` automatically** — writes need server-fresh transaction metadata, so live reads are coupled to write mode | 🔒 Browser session | 🌐 HTTPS per request | +19 write tools, on top of the 25 live read tools |
Passing `--write` implies `--live-reads`; you can still pass `--live-reads` on its own for read-only live access.
📖 **See [docs/tools-by-mode.md](docs/tools-by-mode.md)** for the full per-tool inventory with status, caveats, and known limitations (goals, stock splits, response-size caps).
⬆️ **Upgrading from v2?** v3.0.0 changed the shape of read responses — see **[docs/MIGRATING-v3.md](docs/MIGRATING-v3.md)**. No tool was renamed or removed; rows got terse and two arguments were retired.
---
## Quick Start
### Prerequisites
- **Node.js 18+** (comes bundled with Claude Desktop)
- **Copilot Money** (macOS App Store version)
- **Claude Desktop**, **Cursor**, or any MCP-compatible client
- **For `--live-reads` or `--write` only:** a supported browser logged in to
[app.copilot.money](https://app.copilot.money) — the Chromium family, Firefox, or
Safari; see [docs/graphql-live-reads.md](docs/graphql-live-reads.md) for the list.
The server reuses that session's Firebase refresh token. Without it the server still
starts, and the first live or write tool call tells the assistant to log in — log in
and retry, no restart needed. Default mode needs nothing — it reads the local cache
and makes no network requests.
### Installation via Claude Desktop
1. Download the latest `.mcpb` bundle from [Releases](https://github.com/ignaciohermosillacornejo/copilot-money-mcp/releases)
2. Double-click the `.mcpb` file to install in Claude Desktop
3. Restart Claude Desktop
4. Start asking questions about your finances!
### Installation via npm
```bash
npm install -g copilot-money-mcp
```
Then add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"copilot-money": {
"command": "copilot-money-mcp"
}
}
}
```
### Installation for Cursor
1. Install the package globally:
```bash
npm install -g copilot-money-mcp
```
2. Open Cursor Settings (`Cmd + ,`) > **Features > MCP Servers**
3. Add the server configuration:
```json
{
"mcpServers": {
"copilot-money": {
"command": "copilot-money-mcp"
}
}
}
```
## What You Can Do
### Spending Analysis
> "How much did I spend on dining out last month?"
> "Show me all my Amazon purchases in the last 30 days"
> "What are my top 5 spending categories this year?"
Uses `get_transactions` (or `get_transactions_live` for fresh data via `--live-reads`) and `get_categories` (or `get_categories_live`), with date ranges, text search, and category filters.
### Account Overview
> "What's my net worth across all accounts?"
> "Show me my checking account balance over the past 6 months, monthly"
> "Which bank connections need attention?"
Uses `get_accounts` (or `get_accounts_live` for fresh balances via `--live-reads`), `get_balance_history` (with optional `get_balance_history_live` per-account live variant), `get_connection_status`, and `get_networth_live` for net-worth-over-time charts.
### Investment Portfolio
> "What are my current holdings and total returns?"
> "Show me the price history for my largest equity holding over the past year"
> "What's my current cost basis on META?"
Uses `get_holdings` (or `get_holdings_live` for live cost basis), `get_investment_prices` (or `get_investment_prices_live` for live per-security price history).
### Budgets & Goals
> "Am I on track with my budgets this month?"
> "How is my emergency fund progressing?"
> "Show me my goal history over the past 6 months"
Uses `get_budgets` (or `get_budgets_live` via `--live-reads`), and `get_goals` / `get_goal_history` (cache-only — Copilot's GraphQL endpoint doesn't expose goal data).
### Subscriptions & Recurring
> "What subscriptions am I paying for?"
> "How much do I spend on recurring charges per month?"
Uses `get_recurring_transactions` (or `get_recurring_live` via `--live-reads`) and `get_upcoming_recurrings_live` for next-due unpaid items.
## Configuration
### Cache TTL
The server caches data in memory for 5 minutes. Configure via environment variable:
```bash
# Set cache TTL to 10 minutes
COPILOT_CACHE_TTL_MINUTES=10 copilot-money-mcp
# Disable caching (always reload from disk)
COPILOT_CACHE_TTL_MINUTES=0 copilot-money-mcp
# Write tools resolve account/item ids for a transaction id via a live
# window fetch when the in-memory index (fed by live reads) misses.
# Default window: 13 months. Raise it to edit older transactions:
COPILOT_WRITE_RESOLVE_WINDOW_MONTHS=30 copilot-money-mcp --write
# Write-resolution routing ids (opaque transaction/account/item ids only —
# never amounts or names) persist whenever live reads are enabled (including
# --write mode), keyed by the logged-in account, so cross-session writes
# skip the live window fetch.
# The same switch also disables the persisted split-parent record
# (split-parents.<uid>.jsonl, opaque ids only): delete_transaction then
# remembers a parent split through this server only until it restarts, and
# after a restart relies on the local cache to refuse it (#842).
# Disable both entirely:
COPILOT_DISABLE_PERSISTENT_INDEX=1 copilot-money-mcp --write
```
You can also refresh manually using the `refresh_database` tool.
### Decode Timeout
For large databases (500MB+), increase the decode timeout (default: 90 seconds):
```bash
# Via environment variable
DECODE_TIMEOUT_MS=600000 copilot-money-mcp
# Via CLI flag
copilot-money-mcp --timeout 600000
```
For databases over 1GB, also increase Node.js memory:
```json
{
"mcpServers": {
"copilot-money": {
"command": "node",
"args": [
"--max-old-space-size=4096",
"/path/to/copilot-money-mcp/dist/cli.js",
"--timeout", "600000"
]
}
}
}
```
### Supported Date Periods
The `period` parameter supports these shortcuts:
`this_month` `last_month` `last_7_days` `last_30_days` `last_90_days` `ytd` `this_year` `last_year`
## Authentication & Optional Modes
Both `--live-reads` and `--write` make authenticated calls to Copilot Money's GraphQL API at `app.copilot.money/api/graphql`. They require a **logged-in browser session** against `app.copilot.money` — the server reads the same Firebase refresh token the web app stores in your browser. Ten browsers are searched — Chrome, Arc, Microsoft Edge, Brave, Vivaldi, Chromium, Opera, Opera GX, Safari, and Firefox — and each Chromium-family browser across every one of its profiles, not just the default one. See [PRIVACY.md](PRIVACY.md) for exactly which files are read and why the browser-wide `Local Storage` read is not scoped to Copilot's origin.
On **Linux** the Linux locations are searched instead: Chrome (`~/.config/google-chrome`, plus `-beta` / `-unstable`), Microsoft Edge (`~/.config/microsoft-edge`), Brave (`~/.config/BraveSoftware/Brave-Browser`), Vivaldi (`~/.config/vivaldi`), Chromium (`~/.config/chromium` and the snap's `~/snap/chromium/common/chromium`), Opera (`~/.config/opera`), and Firefox (`~/.mozilla/firefox`, `~/.config/mozilla/firefox` where Firefox 147+ creates new profiles when there is no `~/.mozilla`, the snap's `~/snap/firefox/common/.mozilla/firefox`, and the flatpak's `~/.var/app/org.mozilla.firefox/.mozilla/firefox`). The `~/.config` locations honour `XDG_CONFIG_HOME`. Arc, Safari and Opera GX are macOS-only.
#### Choosing the browser and profile: `--browser` / `--browser-profile`
By default the browsers above are tried in that fixed order and the first working session wins. If more than one Copilot account is logged in on the machine — say one person in Brave and another in Chrome — pin the one to use:
| Flag | Environment variable | Value |
| --- | --- | --- |
| `--browser <name>` | `COPILOT_BROWSER` | `chrome`, `arc`, `edge` (or `microsoft-edge`), `brave`, `vivaldi`, `chromium`, `opera`, `opera-gx` (or `operagx`), `safari`, `firefox` — case-insensitive |
| `--browser-profile <name>` | `COPILOT_BROWSER_PROFILE` | Chromium family: the profile directory (`Default`, `Profile 2`) or the profile's display name as shown in the browser. Firefox: the profile directory (`abcd1234.default-release`), the part after its first dot (`default-release`), or its `Name=` in `profiles.ini`. Case-insensitive. A name that matches more than one profile is refused (see below). Prefix `dir:` to match only the exact, case-sensitive directory name (`dir:Profile 1`), or `name:` to match only display names / `Name=` (a name that itself starts with `dir:` or `name:` takes the prefix twice, e.g. `name:name:Work`). On Linux a Chrome profile name matches in each of the stable, beta and unstable channels, and every match is searched. Requires a browser. |
| `--browser-data-dir <path>` | `COPILOT_BROWSER_DATA_DIR` | A custom user-data root that **replaces** the browser's default locations — for a browser launched with `--user-data-dir`, a portable install, or a copied profile. Chromium family: the folder holding `Local State` and the `Default` / `Profile N` dirs (Opera: also its root-layout storage). Firefox: a profiles root (holding `profiles.ini` and/or the profile dirs, directly or under `Profiles/`), or a single profile directory (one holding `storage/default`, `prefs.js` or `times.json`). Use an **absolute path**: a leading `~` is expanded and a relative path resolves against the server's working directory, which MCP hosts often set to `/`. Requires a browser; not supported for Safari. |
Each flag wins over its environment variable. With a browser selected, **only that browser** (and, with a profile, only that profile) is read — there is no fallback to the others, so the session can never silently come from someone else's browser. If nothing is found, the error names exactly what was searched, e.g. `No Copilot Money session found in Brave (profile "Default"). …`, and says so when the browser's data directory or the profile does not exist.
A profile name must pick **exactly one** profile. Display names are not unique, and Microsoft Edge's built-in ones are off by one from its directories (directory `Default` is shown as "Profile 1", directory `Profile 1` as "Profile 2"), so a name that matches one profile's directory and another's display name — or two display names, or two Firefox profiles — is refused, and nothing is searched: `--browser-profile "Profile 1" is ambiguous in Microsoft Edge: it matches directory "Profile 1" and the display name of directory "Default". Pick exactly one with --browser-profile "dir:Default" or "dir:Profile 1".` Every value the refusal offers selects exactly one profile: a Firefox profile that `profiles.ini` keeps outside the profiles directory is offered as `name:` instead, since `dir:` only sees listed directories, and two Firefox profiles that no name tells apart (the same directory name directly in a `--browser-data-dir` and under its `Profiles/`, or two `profiles.ini` entries with one `Name=` kept outside the profiles directory) are named by path: select one by dropping `--browser-profile` and pointing `--browser-data-dir` at it. The `dir:` form always names exactly one profile, so on a stock Edge with three or more profiles — where every middle `Profile N` is both a directory and another profile's display name — every profile stays selectable. A directory whose own display name is the same string is not ambiguous. On Linux, an ambiguity inside any one Chrome channel refuses the whole selection, and the message names that channel's directory.
The selection only applies with `--live-reads` or `--write`. There, an unknown browser name, a profile without a browser, a macOS-only browser on Linux, or a bad `--browser-data-dir` stops the server at startup with an error. In the default cache-only mode no browser is read, so a selection — from flags or from the environment, valid or not — is ignored with a one-line warning on stderr instead of stopping the server.
With `--browser-data-dir`, only that directory is read — not the browser's default locations and not any other browser. `--browser-profile` then matches profiles inside it (directory name, `Local State` display name, Firefox suffix or `profiles.ini` `Name=`, with relative `Path=` entries resolved against the data dir). Pointed at a single Firefox profile, no `--browser-profile` is needed, and one that names a different profile is refused. The server refuses to start if the path does not exist or is not a directory, if no browser is given, or if the browser is Safari (whose locations are fixed by macOS). If the directory holds no session, the error names it: `No Copilot Money session found in Chrome (data dir "/home/me/chrome-profile/Fork-18"). …`, and if the path looks like one Chromium profile (it is named `Default` / `Profile N` or holds a `Preferences` file) rather than the user-data root above it, the error says so and suggests the parent plus `--browser-profile`.
```json
{
"mcpServers": {
"copilot-money": {
"command": "copilot-money-mcp",
"args": ["--live-reads", "--browser", "brave", "--browser-profile", "Default"]
}
}
}
```
Or, equivalently, `"env": { "COPILOT_BROWSER": "brave", "COPILOT_BROWSER_PROFILE": "Default" }` in the same entry. For a Chrome started with a custom `--user-data-dir`:
```json
"args": ["--live-reads", "--browser", "chrome", "--browser-data-dir", "/home/me/chrome-profile/Fork-18"]
```
Default mode requires no authentication and makes zero network requests — reads come from the local LevelDB cache.
### `--live-reads`: real-time reads via GraphQL
```bash
copilot-money-mcp --live-reads
```
Replaces 6 cache-mode read tools (`get_transactions`, `get_accounts`, `get_categories`, `get_budgets`, `get_recurring_transactions`, `get_holdings`) with live GraphQL-backed equivalents, and adds 11 net-new ones (`get_tags_live`, `get_networth_live`, `get_upcoming_recurrings_live`, `get_monthly_spend_live`, `get_balance_history_live`, `get_investment_prices_live`, `refresh_cache`, `get_investment_allocation_live`, `get_top_movers_live`, `get_aggregated_holdings_live`, `get_investment_balance_live`).
Use this when:
- You need transactions the macOS app hasn't pre-fetched yet (the auto-fetch window is typically ~30 days; past that, open the app and scroll back to force the cache to populate, or use `--live-reads` to query the server directly).
- You want fresh per-security cost basis or balance-over-time data.
- The macOS app hasn't synced recently.
### `--write`: mutations via GraphQL (implies `--live-reads`)
```bash
copilot-money-mcp --write
```
Adds 19 mutation tools for transactions, tags, categories, recurrings, budgets, and split-transactions. Off by default — the server is read-only unless you opt in.
`--write` automatically enables `--live-reads` as well. Write tools resolve transaction metadata (account/item IDs) against the live GraphQL surface so they can edit any transaction the API exposes, not just the ~30 days the local LevelDB cache happens to hold. Once you've consented to the authenticated network calls writes require, there's no privacy or perf reason to keep reads pinned to the stale cache.
```bash
copilot-money-mcp --write --live-reads # equivalent to `--write` alone; --live-reads is redundant
```
### Configuring via Claude Desktop / Cursor
Add the flags to the `args` array in your MCP config:
```json
{
"mcpServers": {
"copilot-money": {
"command": "copilot-money-mcp",
"args": ["--write"]
}
}
}
```
Restart Claude Desktop / Cursor after editing.
## Known Limitations
### Local Cache Dependency
This server reads from Copilot Money's **local Firestore cache**, not the cloud. Firestore's offline persistence caches every document the app has ever fetched, so the local database generally contains all transactions, accounts, budgets, goals, and other data you've viewed in the app. The default Firestore cache size is 100 MB (enough for tens of thousands of transactions), and older documents are only evicted via LRU garbage collection if that limit is exceeded.
**To maximize cached data:** Open the Copilot Money app and browse through your data (transaction history, accounts, budgets) to ensure it has been fetched and cached locally.
### Goals are read-only
`get_goals` and `get_goal_history` work (cache-only), but there are no goal write tools — Copilot's GraphQL endpoint doesn't expose goal mutations. Goal creation, editing, and contributions are mobile-only in Copilot itself, and live in a path our project can't reach without iOS / desktop traffic capture.
### Investment splits are limited to currently-held securities
`get_investment_splits` returns split events (date + adjustment multiplier) for securities you currently hold. Securities you no longer hold eventually fall out of the cache. There's no GraphQL endpoint for splits, so this is the only path.
Also: `get_investment_prices` and `get_investment_prices_live` already return split- and dividend-adjusted prices (Copilot applies Plaid's adjustment factors server-side). You generally don't need raw split events to back-correct prices.
### Live investment prices are ownership-gated
`get_investment_prices_live` only works for securities currently in your linked accounts. Asking for a price series on a ticker you don't own returns an explicit "not in your linked accounts" error.
### Long time-series responses are capped
Time-series live tools (`get_balance_history_live`, `get_networth_live`, `get_investment_prices_live`) cap responses at 500 rows by default to fit the MCP single-tool-result token limit. Pass `max_rows` / `offset` to paginate, or narrow `time_frame` for fewer rows.
`get_investment_balance_live` has its own, much tighter cap under a different name: `history_limit`, default **30** points, `0` for the whole series. Its responses always report `history_total_count` and `history_truncated`, and its `current` value is resolved separately so it can never be lost to truncation.
## Troubleshooting
### Database Not Found
If you see "Database not available":
1. Ensure Copilot Money is installed and has synced data
2. Check the database location: `~/Library/Containers/com.copilot.production/Data/Library/Application Support/firestore/__FIRAPP_DEFAULT/copilot-production-22904/main`
3. Verify `.ldb` files exist in the directory
4. Provide a custom path: `copilot-money-mcp --db-path /path/to/database`
### Decode Worker Timed Out
If you see "Decode worker timed out":
1. Increase the timeout: `copilot-money-mcp --timeout 300000` (5 minutes)
2. For 1GB+ databases, also increase Node.js memory: `node --max-old-space-size=4096 dist/cli.js --timeout 300000`
### No Transactions Found
- Open the Copilot Money app and wait for sync
- The database structure may have changed — [open an issue](https://github.com/ignaciohermosillacornejo/copilot-money-mcp/issues)
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, architecture, and how to add new tools.
## License
MIT License - See [LICENSE](LICENSE) for details.
## Acknowledgments
- Built with [MCP SDK](https://modelcontextprotocol.io/) by Anthropic
- Data validation with [Zod](https://zod.dev/)
- Developed with [Bun](https://bun.sh/)
TDQS
Scored across 14 tools
Each tool targets a distinct resource (transactions, accounts, holdings, budgets, goals, categories, splits, connection/cache status), and the descriptions steer callers correctly for the closest pairs (get_goals vs get_goal_history, get_accounts vs get_balance_history, get_cache_info vs get_connection_status). The main ambiguity comes from get_transactions being a six-mode mega-tool and from multiple tools referencing absent `_live` counterparts (get_transactions_live, get_investment_prices_live, get_investment_balance_live), which an agent cannot call.
All tools use snake_case with a get_<resource> pattern, and the single exception (refresh_database) still follows the verb_noun convention. Resource names are predictable and parallel across domains (get_goals/get_goal_history, get_holdings/get_investment_prices).
14 tools is well within the sweet spot for a personal-finance server covering transactions, accounts, investments, budgets, goals, and cache management. Each tool maps to a real domain object and none look redundant or padding.
Read coverage is thorough across every major domain: accounts, transactions (with search/filter/location/tag modes), balance history, budgets, categories, goals plus goal history, holdings, prices, splits, recurring charges, and cache/connection health. The gap is the absence of any mutation tools (edit transaction category, adjust budget/goal, create tags), so agents can analyze but not change anything.