Skip to main content
Glama
ynab-mcp

ynab-mcp

by JasonSooter
README.md
# ynab-mcp

Your [YNAB](https://www.ynab.com) budget as MCP tools, reachable from every
Claude client (Claude Code, Desktop, claude.ai and mobile), behind a password +
TOTP login.

The tools are [`mcp-ynab`](https://github.com/pragprogrammer/mcp-ynab) by
Michael Green, installed unchanged from PyPI at the version `uv.lock` pins.
Upstream serves them over stdio only, which no phone or browser can reach. This
project adds the two things a hosted server needs:

- **Streamable HTTP** at `/mcp`, served by uvicorn.
- **OAuth 2.1 with a password + TOTP login**, which claude.ai and the mobile apps
  need to add it as a connector. The token behind it can edit the budget, so the
  server refuses to start without both factors. The OAuth code is shared with
  [anki-mcp](https://github.com/JasonSooter/anki-mcp) and
  [obsidian-mcp](https://github.com/JasonSooter/obsidian-mcp).

## How it reuses upstream

Upstream builds an `MCPServer` and registers its tools on it at import time.
`src/ynab_mcp/server.py` imports that, takes its tool list and passes it to a new
`MCPServer` that has auth configured. No tool code is copied or changed. Reading
the tool list touches one private SDK attribute; `tests/test_server.py` checks
that every tool upstream exports is still served, so a breaking bump fails CI.

## Run it

```bash
docker run -d --name ynab-mcp \
  --env-file secrets.env \
  -e YNAB_MCP_PUBLIC_URL=https://ynab.example.ts.net \
  -v "$PWD/data:/data" \
  -p 127.0.0.1:8790:8790 \
  ghcr.io/jasonsooter/ynab-mcp:main
```

| variable | default | meaning |
|---|---|---|
| `YNAB_API_KEY` | required | YNAB personal access token |
| `YNAB_MCP_LOGIN_PASSWORD` | required | login password, ≥ 12 characters |
| `YNAB_MCP_TOTP_SECRET` | required | base32 TOTP setup key |
| `YNAB_MCP_PUBLIC_URL` | `http://localhost:8790` | the public HTTPS URL; feeds OAuth discovery |
| `YNAB_MCP_STATE_DIR` | `/data` | holds `oauth.db` (connector logins) and `cache.db` (upstream's cache) |
| `YNAB_MCP_PORT` | `8790` | listen port |

`/data` must persist. Without it, every device has to add the connector again
after a restart. Put the endpoint behind HTTPS (e.g. Tailscale Funnel) before
adding it to Claude, and add it as `https://<host>/mcp`.

## Develop

```bash
uv sync
uv run pytest
```

CI runs the tests, then builds the image for amd64 and arm64, and pushes it to
`ghcr.io/jasonsooter/ynab-mcp` on `main` (tags `main` and `sha-<commit>`).

## License

AGPL-3.0-only, the same as upstream mcp-ynab, whose code the image contains.

TDQS

A3.7/5.0

Scored across 47 tools

Disambiguation4/5

Most tools target a distinct resource+action, but there is a large family of transaction retrievers (list_transactions, get_transactions_by_account/category/month/payee, search_transactions) whose boundaries overlap and require careful reading to select correctly. Similarly, get_category vs get_category_for_month and the money-movement list/for_month pairs are close but distinguishable via their descriptions.

Naming Consistency4/5

The dominant pattern is a clean verb_noun convention (get_plan, list_accounts, create_transaction, update_category, delete_transaction). However several tools deviate with 'by_x' / 'for_month' suffixes (get_transactions_by_account, get_category_for_month, get_money_movements_for_month) and the singular/plural bulk distinction (create_transaction vs create_transactions), which are minor inconsistencies.

Tool Count2/5

At 47 tools the surface is very heavy and exceeds the threshold where counts start hurting selection. While YNAB is a broad domain, the count is inflated by consolidation opportunities (multiple transaction-fetch variants, separate money-movement/money-movement-group/month endpoints), pushing it beyond a well-scoped set.

Completeness4/5

Coverage is strong across plans, accounts, transactions (including bulk create/update and import), categories, category groups, payees, payee locations, months, scheduled transactions, and analytics (money flow, spending by category). Minor gaps exist, notably no delete_account, delete_category, delete_category_group, or explicit payee lifecycle operations.

Maintenance

ActivityMaintained
ResponsivenessLimited data