MCP Server for YNAB
An AI-first Model Context Protocol server for YNAB. It exposes the YNAB API as MCP tools, then adds enriched tools that answer questions the raw API cannot answer in one call — budget health, cleanup queues, spending analysis.
Read-only by default. Write tools are not registered unless you set
YNAB_ALLOW_WRITES=1, so an agent cannot change your budget until you say so.
See Write Tools.
Quick Start
1. Get a YNAB token
Generate a personal access token at app.ynab.com/settings/developer.
You also need uv, which provides uvx:
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS and Linux
brew install uv # macOS with Homebrew
winget install --id=astral-sh.uv -e # Windows
Check both work. Nothing to clone — uvx fetches the package and runs it in a
throwaway environment:
YNAB_API_KEY=your_token uvx mcp-server-for-ynab smoke
smoke validates configuration and tool registration, then exits. It should
print smoke: app created, 44 tools registered.
2. Add it to your client
| Client | Setup |
|---|---|
| Claude Code | one command |
| Claude Desktop | config file |
| Cursor | one-click link |
| VS Code (GitHub Copilot) | one command |
| Codex CLI | one command |
| Gemini CLI | one command |
| Windsurf, Zed, others | generic stdio config |
| MCP Inspector | debugging |
The two most common paths:
Claude Code — one command:
claude mcp add --env YNAB_API_KEY=your_ynab_token --transport stdio --scope user \
ynab -- uvx mcp-server-for-ynab stdio
Claude Desktop, Cursor, Windsurf, and most others — paste into the client's MCP config file:
{
"mcpServers": {
"ynab": {
"command": "uvx",
"args": ["mcp-server-for-ynab", "stdio"],
"env": {
"YNAB_API_KEY": "your_ynab_token",
"YNAB_PLAN_ID": "your_plan_id"
}
}
}
}
YNAB_PLAN_ID is optional but recommended — with it set, you never have to
name a budget in a request. Full per-client instructions, including where each
config file lives and how to keep the token out of it, are in
Client Setup.
3. Ask it something
What's my cash position across all accounts?
If that works, you're set. If it doesn't, see
Troubleshooting — the usual cause is
that the client cannot find uvx on its PATH.
What You Can Ask
Read-only, works out of the box:
| Ask | Tool it reaches for |
|---|---|
| "How is this month's budget doing?" | overview_month_health |
| "What's my cash position across all accounts?" | overview_cash_position |
| "Which transactions still need a category?" | triage_uncategorized |
| "What's waiting for me to approve?" | triage_unapproved |
| "Which categories are overspent, and by how much?" | analysis_overspent_categories |
| "Which targets won't be funded this month?" | analysis_target_funding_gaps |
| "Are any scheduled transactions at risk?" | analysis_upcoming_scheduled_risks |
| "What have I spent at this payee over the last year?" | bookkeeping_transaction_history |
With YNAB_ALLOW_WRITES=1:
Categorize last week's uncategorized transactions, then show me what you changed.
The agent categorizes, and history_list shows every write with the value that
preceded it. history_revert undoes any of them.
Agents work best when they start with overview_available_tools, which returns
the current tool catalog grouped by family. See
Tool Surface for the full map.
Configuration
| Variable | Required | Description |
|---|---|---|
YNAB_API_KEY |
Yes | YNAB personal access token |
YNAB_PLAN_ID |
Recommended | Default plan ID, making plan_id optional on most tools |
YNAB_ALLOW_WRITES |
No | Register write tools. Unset means read-only |
YNAB_HISTORY_PATH |
No | Write history file, default ~/.mcp-server-for-ynab/history.jsonl |
YNAB_RATE_LIMIT_PER_HOUR |
No | Client-side request budget, default 190 of YNAB's 200 |
YNAB_RATE_WARN_THRESHOLD |
No | Warn when this many requests remain, default 50 |
LOG_LEVEL |
No | Logging verbosity, default INFO |
Set these in your MCP client's env block. For local development, copy
.env.example to .env and fill it in.
Rate limits
YNAB allows 200 requests per hour per token, and a single enriched tool can
spend several. The server tracks its own usage in a rolling hour and stops just
below YNAB's ceiling, so the limit you hit is local and clearly reported rather
than a 429 in the middle of a workflow. Call overview_request_budget to see
what is left; it costs no API requests.
Tool Families
44 read-only tools, 63 with writes enabled.
| Family | Type | Purpose |
|---|---|---|
overview |
enriched | Budget health snapshots and orientation |
triage |
enriched | Transaction cleanup queues |
bookkeeping |
enriched | Categorization suggestions, memo help, history |
analysis |
enriched | Spending analysis, funding gaps, scheduled risks |
history |
enriched | Review and roll back writes this server made |
user |
raw | YNAB user info |
plans |
raw | Plan and settings reads |
accounts |
raw | Account reads and creation |
categories |
raw | Categories and category groups |
months |
raw | Month-level budget data |
payees |
raw | Payee management |
payee_locations |
raw | Geographic payee metadata, niche/low-priority |
transactions |
raw | Transaction CRUD and import trigger |
scheduled_transactions |
raw | Scheduled transaction management |
money_movements |
raw | Money movement data |
Raw tools are close mirrors of YNAB endpoints — use them for exact reads and
all writes. Enriched tools combine several reads into one answer — use them
for orientation, investigation, and analysis. Every tool is labeled read or
write, and enriched tools perform no hidden writes.
More detail: Tool Surface
Write Tools
Write tools are not registered unless you opt in:
YNAB_ALLOW_WRITES=1
Without it the server is read-only, and the write tools are absent from
tools/list — an agent cannot call what it cannot see. This is deliberate: the
server holds a credential that can modify real financial records, and refusing a
call at execution time would still advertise the capability.
Every write is recorded, and most can be undone
When writes are enabled, each one records the state that existed before it. YNAB has no history endpoint, so this is the only way to get an overwritten value back.
| Tool | Purpose |
|---|---|
history_list |
Recent writes, newest first, each marked revertible or not |
history_show |
One entry in full, including the before state |
history_revert |
Undo one write |
history_revert_to |
Roll the plan back to its state at a chosen entry |
history_revert_to undoes everything after the entry you name, newest first,
because overlapping edits to the same record only compose correctly in reverse.
Reverting is itself recorded, so a revert can be reverted.
What cannot be undone. YNAB has no delete route for accounts, categories,
category groups, or payees, so creating one is permanent. Those operations are
recorded as non-revertible with the reason, and a rollback reports them under
blocked rather than skipping them silently — an incomplete rollback that
claims success is worse than one that tells you what it left behind. A
recreated transaction also gets a new id and loses any bank-import link.
Writes are checked, not assumed
Tools that change a value re-read it afterwards and report a verification
block. A 200 response is not proof: YNAB accepts budgeted on the category
update route, returns 200, and ignores it. Verification is what catches that.
Amount Convention
All YNAB monetary amounts are in milliunits: 1000 = $1.00.
- Raw tools accept and return milliunits for canonical amount fields.
- Enriched tools may include display helpers alongside canonical values.
Your Data
This server stores exactly one thing on your machine: a record of the writes it
made, used by history_revert. Nothing is sent anywhere except api.ynab.com,
and there is no telemetry.
uvx mcp-server-for-ynab history --show # where it is, how much is there
uvx mcp-server-for-ynab history --export out.json
uvx mcp-server-for-ynab history --delete # also removes the ability to revert
These need no credentials and no agent: getting your data back, or gone, should not require running an LLM.
For Contributors
This repo is structured so a contributor or AI agent can answer three questions quickly: where the MCP server lives, where the YNAB API wrappers and models live, and where to add new tools, tests, and docs.
flowchart LR
A["MCP Client"] --> B["FastMCP Server"]
B --> C["Tool Handlers"]
C --> D["ynab_client"]
D --> E["http_client (httpx)"]
E --> F["YNAB API"]
C --> G["enriched/"]
G --> D
The code is centered on a small set of layers:
src/mcp_server_for_ynab/server/: FastMCP app, tool metadata, tool registration, error boundarysrc/mcp_server_for_ynab/ynab_client/: one async wrapper module per YNAB resource familysrc/mcp_server_for_ynab/http_client/: outboundhttpxwrapper with retries, redaction, and error normalizationsrc/mcp_server_for_ynab/models/: typed YNAB shapes, shared error model, milliunit helperssrc/mcp_server_for_ynab/enriched/: higher-level read-only workflows built on top of raw clientstests/: unit, contract, integration, and QA/Postman source assets
Run it from a clone:
git clone https://github.com/hs737/mcp-server-for-ynab
cd mcp-server-for-ynab
uv sync
cp .env.example .env # then set YNAB_API_KEY
make smoke-stdio
make run-stdio
make run-http
Run the tests:
make test
make test-unit
make test-contract
make test-integration
make test-postman-operator
Where to read next
If you are:
- connecting a client: Client Setup
- new to the repo: Architecture
- adding code: Contributing, Repo Structure, Agent Guidance
- adding or changing tools: Tool Surface
- verifying behavior: Testing
- working on auth, error handling, or logging: Security
Full map: Docs Index. Also: Postman Notes, Legal Notice.
Current State
The current implementation uses Python 3.12, FastMCP from the official mcp
package, asyncio end to end, httpx for outbound YNAB calls, and the built-in
stdio and streamable HTTP transports.
This is a local, personal-access-token server. A hosted or public connector is not implemented — that includes ChatGPT custom connectors, which require a remote HTTPS endpoint rather than a local process. The intent is for a hosted runtime to live in its own repository, importing this package through its embed surface so OAuth and public-app concerns stay out of here.
If architecture and implementation ever diverge, the source of truth should be Architecture, updated to reflect the actual code.
License
Apache License 2.0. See LICENSE and NOTICE.md.
Disclaimer
We are not affiliated, associated, or in any way officially connected with YNAB or any of its subsidiaries or affiliates. The official YNAB website can be found at https://www.ynab.com.
The names YNAB and You Need A Budget, as well as related names, tradenames, marks, trademarks, emblems, and images are registered trademarks of YNAB.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_server_for_ynab-0.1.0.tar.gz.
File metadata
- Download URL: mcp_server_for_ynab-0.1.0.tar.gz
- Upload date:
- Size: 224.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c2595588f64a0d75ee6f3e0cb08303fad6964b82115edfe3706e621423ab8c5
|
|
| MD5 |
8f6b9825d928d6d88c72339fd964e100
|
|
| BLAKE2b-256 |
d1454f892a08e251045e266d7022f1885c5b656dedf19ce9145b0147e0b4720d
|
Provenance
The following attestation bundles were made for mcp_server_for_ynab-0.1.0.tar.gz:
Publisher:
release.yml on hs737/mcp-server-for-ynab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_server_for_ynab-0.1.0.tar.gz -
Subject digest:
5c2595588f64a0d75ee6f3e0cb08303fad6964b82115edfe3706e621423ab8c5 - Sigstore transparency entry: 2625755867
- Sigstore integration time:
-
Permalink:
hs737/mcp-server-for-ynab@334f4ab7e1cc6f2cbdf37f746e153580848f9b77 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hs737
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@334f4ab7e1cc6f2cbdf37f746e153580848f9b77 -
Trigger Event:
push
-
Statement type:
File details
Details for the file mcp_server_for_ynab-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mcp_server_for_ynab-0.1.0-py3-none-any.whl
- Upload date:
- Size: 84.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
225ca5750d70b710e5fe318ef6ff2742e21c686f9baa58dc15bd9aae802ef262
|
|
| MD5 |
98e0215cee8280f5f30f4786973fe3f3
|
|
| BLAKE2b-256 |
aa11901aacaff8352ba45085c7f503deb21e2faa0beecf02835e93dd265ccebc
|
Provenance
The following attestation bundles were made for mcp_server_for_ynab-0.1.0-py3-none-any.whl:
Publisher:
release.yml on hs737/mcp-server-for-ynab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp_server_for_ynab-0.1.0-py3-none-any.whl -
Subject digest:
225ca5750d70b710e5fe318ef6ff2742e21c686f9baa58dc15bd9aae802ef262 - Sigstore transparency entry: 2625755870
- Sigstore integration time:
-
Permalink:
hs737/mcp-server-for-ynab@334f4ab7e1cc6f2cbdf37f746e153580848f9b77 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hs737
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@334f4ab7e1cc6f2cbdf37f746e153580848f9b77 -
Trigger Event:
push
-
Statement type: