A Model Context Protocol server for the YNAB (You Need A Budget) API. Lets MCP clients (Claude Desktop, Claude Code, custom agents) read your budgets, accounts, and transactions, and create or recategorize transactions through structured tools.
The project is managed with uv and
Task. All Python commands in this repo go through
uv run.
uv sync # install dependencies
task install # install the mcp-ynab CLI into the venvSet your YNAB Personal Access Token in the environment (or in a .env file at
the repo root):
export YNAB_API_KEY=your-personal-access-tokenGet a token at https://app.ynab.com/settings/developer.
Code Mode is the default surface — it requires your YNAB API key:
export YNAB_API_KEY="your-key-here"Or store it in your OS keychain (the server reads it automatically).
By default the server exposes two tools: search (discover available operations) and execute (run a snippet against the live YNAB API).
Discover tools:
# In the search tool:
return [t for t in spec if "transaction" in t["name"]]Execute a query:
# In the execute tool:
return await ynab.read.get_budgets()To access all ~34 direct YNAB tools, set the escape-hatch preference:
set_preference: code_mode_replace_tools = false
mcp-ynab # production
task dev # dev mode + MCP Inspector in the browserBy default, the public MCP tool surface is intentionally small:
| Tool | Purpose |
|---|---|
search |
Discover available YNAB operations without live API access |
execute |
Run a short Python snippet against the live YNAB API |
ping |
Health check the server |
get_preferences |
Inspect server preferences |
set_preference |
Update preferences such as mutation or escape-hatch settings |
set_api_key |
Store a YNAB API key in the OS keychain |
clear_api_key |
Remove the stored YNAB API key |
set_preferred_budget_id |
Cache a preferred budget ID for default-targeted calls |
Use search to discover operations:
return [
{"name": tool["name"], "description": tool["description"]}
for tool in spec
if "category" in tool["name"]
]Then call the operation through execute:
categories = await ynab.read.get_categories()
return [
{"name": category.name, "balance": category.balance}
for group in categories
for category in group.categories[:LIMIT]
]Mutating operations live under ynab.write.* and require
code_mode_mutations_enabled=true:
result = await ynab.write.bulk_categorize(assignments=assignments)
return resultThe underlying direct tools still exist in the internal FastMCP registry so
Code Mode can dispatch through ynab.read.*, generate stubs, and build the
search catalog. They are hidden from the public tool list by default. Set
code_mode_replace_tools=false to restore the full direct-tool surface as an
escape hatch.
Representative internal direct tools include:
Read-only internal tools
| Tool | Purpose |
|---|---|
get_budgets |
List all budgets in markdown |
get_accounts |
List accounts in a budget, grouped by type with summary |
get_account_balance |
Return a single account's current balance in dollars |
get_transactions |
Recent transactions for an account; optional since_date |
get_transactions_needing_attention |
Filter for uncategorized / unapproved transactions |
get_categories |
All categories in a budget grouped by category group |
Mutating internal tools
| Tool | Purpose |
|---|---|
create_transaction |
Create a new transaction in YNAB |
categorize_transaction |
Assign a category to an existing transaction |
set_preferred_budget_id |
Cache a preferred budget ID for default-targeted tools |
cache_categories |
Cache a budget's category list locally |
For account transfers and credit-card payments, use the destination account's
transfer payee ID as payee_id when creating or updating a transaction. The
get_payees tool lists transfer payees with their Transfer Account ID.
Do not pass a Transfer : ... value as payee_name; YNAB rejects or treats
that as a regular payee instead of creating a linked transfer.
Resources
ynab://preferences/budget_id— currently preferred budget IDynab://categories/{budget_id}— cached categories for a budgetynab://code-mode/stubs— generated Python stubs for Code Modeynab://code-mode/examples— curated Code Mode snippets
Code Mode exposes execute, a Python execution tool for multi-step YNAB
workflows. It is enabled by default and controlled through the preferences
code_mode_enabled, code_mode_mutations_enabled, and code_mode_replace_tools.
See src/mcp_ynab/code_mode/README.md for usage, wiring instructions, and runner limits.
task fmt # ruff format + check --fix
task lint # ruff check + format --check (no auto-fix)
task typecheck # mypy
task docstrings # interrogate (fails under 80%)
task test # unit tests only (default; integration excluded)
task coverage # unit tests with coverage reportIntegration tests are gated behind pytest.mark.integration and excluded from
the default task test run. They make real calls to your YNAB account, so they
require YNAB_API_KEY.
YNAB_API_KEY=your-token task test:integrationThe default integration suite is read-only. Tests that mutate data (create_transaction, categorize_transaction) require an additional opt-in:
YNAB_API_KEY=your-token YNAB_INTEGRATION_ALLOW_WRITES=1 \
task test:integrationMIT