monarch-mcp
# monarch-mcp
MCP server bridging Claude (Desktop, Code, or any MCP client) to [Monarch Money](https://www.monarchmoney.com/) for personal-finance analysis and lightweight edits.
Wraps the unofficial [`monarchmoney`](https://github.com/hammem/monarchmoney) Python client.
## Features
| Group | Tools |
| --- | --- |
| Transactions | `list_transactions` (with full filter set), `get_transaction`, `get_transactions_summary` |
| Tags | `list_tags`, `create_tag`, `set_transaction_tags` |
| Categories | `list_categories`, `list_category_groups`, `create_category`, `delete_category` |
| Accounts | `list_accounts` |
| Net worth | `get_net_worth_history`, `get_net_worth_by_type`, `get_account_history` |
| Cash flow | `get_cash_flow`, `get_cash_flow_summary` |
| Budgets / goals | `get_budgets` (includes v2 goals), `set_budget` |
### Upstream API gaps
The following are **not** exposed because the upstream library does not implement them:
- Tags: no rename, no delete.
- Categories: no rename (delete is supported).
- Goals: read-only (returned alongside `get_budgets`); no create/update/delete.
## Install
Requires Python 3.10+ and an OS keychain that the [`keyring`](https://pypi.org/project/keyring/) package can talk to (macOS Keychain, Windows Credential Manager, GNOME Keyring / KWallet via D-Bus on Linux).
### Recommended: pipx
[`pipx`](https://pipx.pypa.io/) installs the package into its own venv and puts the `monarch-mcp` / `monarch-mcp-setup` entry points on your `$PATH`:
```bash
pipx install .
```
### Alternative: pip in a venv
```bash
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install .
```
If you go this route, Claude Desktop will need the absolute path to the venv's `monarch-mcp` binary (see below).
### Linux note
On a headless Linux box (or WSL2 without a desktop session), `keyring` won't find a backend by default. Either:
- install `gnome-keyring` and run `dbus-launch` so the daemon is reachable, or
- install [`keyrings.alt`](https://pypi.org/project/keyrings.alt/) (`pip install keyrings.alt`) and accept that secrets land in a plaintext file under `~/.local/share/python_keyring/` — fine for a personal dev box, not fine for shared machines.
## Configure credentials
Run once to store your Monarch email, password, and (optional but recommended) MFA TOTP secret in the OS keychain. All three live under service name `monarch-mcp`.
```bash
monarch-mcp-setup set # interactive prompts (password + MFA are hidden input)
monarch-mcp-setup show # report which fields are stored (values not echoed)
monarch-mcp-setup clear # wipe all three fields
```
`monarch-mcp-setup` with no subcommand defaults to `set`.
### About the MFA secret
When you enable MFA in Monarch, the setup screen shows a QR code *and* a base32 string (often labelled "secret key" or "manual entry code"). That base32 string is what `monarch-mcp-setup` is asking for — not a 6-digit code.
Storing it lets the server compute TOTP codes itself and re-authenticate unattended after the session pickle expires. Without it, every session expiry forces you to manually re-login by clearing credentials and running `set` again with a fresh code.
If you didn't capture the secret when you first enrolled, you can re-enroll your authenticator in Monarch's settings to see it again.
### Where session state lives
After the first successful login, the server caches a session pickle so subsequent process starts skip the login round-trip:
- Default: `~/.config/monarch-mcp/session.pickle`
- Override: set the `MONARCH_MCP_SESSION_DIR` env var to a directory of your choice
If the pickle gets corrupted or rejected by Monarch, the server silently falls back to a fresh login using the stored credentials — you don't need to clear it manually.
## Wire into Claude Desktop
Edit `claude_desktop_config.json`:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- Linux: `~/.config/Claude/claude_desktop_config.json`
Add an entry under `mcpServers`:
```json
{
"mcpServers": {
"monarch": {
"command": "monarch-mcp"
}
}
}
```
If `monarch-mcp` isn't on the `$PATH` Claude Desktop sees (common when you used a venv rather than pipx), give it the absolute path:
```json
{
"mcpServers": {
"monarch": {
"command": "/Users/you/code/monarch-mcp/.venv/bin/monarch-mcp"
}
}
}
```
To park the session pickle somewhere non-default:
```json
{
"mcpServers": {
"monarch": {
"command": "monarch-mcp",
"env": { "MONARCH_MCP_SESSION_DIR": "/Users/you/.local/share/monarch-mcp" }
}
}
}
```
Restart Claude Desktop. The tools should appear in the MCP picker.
## Wire into Claude Code
```bash
claude mcp add monarch monarch-mcp
```
## Notes on the tool surface
- All date arguments are ISO `YYYY-MM-DD` strings.
- IDs (category, tag, account) are opaque strings — discover them via the corresponding `list_*` tool before calling filtered endpoints.
- `set_budget` requires exactly one of `category_id` or `category_group_id`.
- `list_transactions` paginates: pass `limit` and `offset` to walk results larger than the default 100.
## Development & tests
The test suite is hermetic — it stubs `keyring`, `monarchmoney`, and `mcp.server.fastmcp` when those packages aren't installed, so you can run it without network access or a keychain backend.
### Setup
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
```
`-e .[dev]` installs the package editable plus `pytest` and `pytest-asyncio`.
### Run
```bash
pytest # full suite (~80ms, 43 tests)
pytest tests/test_server.py # one module
pytest -k "set_budget" # one keyword
pytest -v # verbose, lists each test
```
Pytest config lives in [pyproject.toml](pyproject.toml) under `[tool.pytest.ini_options]`:
- `pythonpath = ["src"]` — lets `from monarch_mcp import ...` resolve without an editable install (handy in CI containers that don't run `pip install`).
- `asyncio_mode = "auto"` — async test functions don't need a `@pytest.mark.asyncio` decorator.
- `testpaths = ["tests"]` — bare `pytest` finds the suite.
### Layout
```
src/monarch_mcp/
auth.py — keychain wrapper (get/set/delete + require_login_credentials)
setup.py — monarch-mcp-setup CLI (set / show / clear)
server.py — FastMCP server, lazy-login client, all tool definitions
tests/
conftest.py — third-party stubs + clean_keyring / fake_mm_client / server_with_fake_client fixtures
test_auth.py
test_setup.py
test_server.py
```
### Writing a new tool
1. Add an `@mcp.tool()`-decorated async function to [src/monarch_mcp/server.py](src/monarch_mcp/server.py). The docstring becomes the MCP tool description Claude sees, so write it for Claude.
2. If it calls a new `MonarchMoney` method, add the method name to `_FAKE_METHODS` in [tests/conftest.py](tests/conftest.py) so `fake_mm_client` mocks it out.
3. Add a test in [tests/test_server.py](tests/test_server.py) using the `server_with_fake_client` fixture — assert on `client.<method>.await_args.kwargs` to confirm the call shape.
### What the fixtures do
- `clean_keyring` — clears the in-memory keyring stub before each test (no-op when real keyring is installed).
- `fake_mm_client` — `MagicMock` whose Monarch methods are `AsyncMock`s returning `{"called": <method_name>}`.
- `server_with_fake_client` — monkeypatches `server._client` to the fake, so tool calls bypass `_get_client()` and the login flow entirely. Tests that *do* exercise login (`test_get_client_*`) reset `_client` to `None` and patch `server.MonarchMoney` directly.
TDQS
Scored across 18 tools
Each tool targets a distinct financial entity or operation (e.g., categories, tags, budgets, transactions, cash flow, net worth). No two tools have overlapping functionality; even related tools like get_cash_flow and get_cash_flow_summary are clearly differentiated by detail level.
All tool names follow a consistent verb_noun pattern using snake_case (e.g., create_category, get_budgets, list_transactions). Verbs are uniformly chosen (create, delete, get, list, set) and nouns are plural or singular as appropriate.
18 tools cover the major areas of personal finance management (accounts, transactions, categories, tags, budgets, cash flow, net worth) without being overwhelming. The count is within the optimal 3-15 range, though slightly above, but still well-scoped for the domain.
The tool surface has significant gaps: no update or delete for transactions, no delete for tags, no update for categories or tags. While core reads and some writes exist, essential lifecycle operations (especially delete and update for transactions) are missing, limiting agent workflows.