Install

Set up ynab-mcp

About five minutes: get a token from YNAB, add a few lines to your Claude configuration, restart.

Requirements

  • A YNAB account.
  • Node.js 22.14.0 or later for the npx and source options. Check with node -v. Or use Docker instead, which needs no Node.js.
  • An MCP client: Claude Desktop, Claude Code, or another client that supports MCP servers.
Package name

The package is ynab-mcp. The npm package ynab-mcp-server is an unrelated project.

Get a YNAB personal access token

  1. Sign in to YNAB on the web and open Account Settings, Developer Settings.
  2. Under Personal Access Tokens, choose New Token and confirm your password.
  3. Copy the token. YNAB shows it only once. Treat it like a password: anyone holding it can read and change your budget.

You can revoke the token on the same page at any time. A revoked token stops working immediately.

Claude Desktop

Open the configuration file from the Claude menu (Settings, Developer, Edit Config), or edit it directly:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Add one of the following, replace your_token_here, save, and quit and reopen Claude Desktop. If the file already has an mcpServers section, add the "ynab" entry inside it.

Using npx (recommended)

{
  "mcpServers": {
    "ynab": {
      "command": "npx",
      "args": ["-y", "ynab-mcp"],
      "env": {
        "YNAB_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}

Using Docker

{
  "mcpServers": {
    "ynab": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "YNAB_ACCESS_TOKEN",
        "ghcr.io/auzroz/ynab-mcp:latest"
      ],
      "env": {
        "YNAB_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}

The -e YNAB_ACCESS_TOKEN argument passes the value from env into the container without putting it on the command line. Image tags follow releases: latest, or a version such as 0.4.2.

Using a local build

{
  "mcpServers": {
    "ynab": {
      "command": "node",
      "args": ["/path/to/ynab-mcp/dist/index.js"],
      "env": {
        "YNAB_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}

See From source for building it.

Claude Code

These commands are for Bash or Zsh; on Windows, run them in WSL or Git Bash. Type this first command rather than copying it, so your token stays on the clipboard, then paste the token at its prompt, which doesn't echo it or keep it in shell history:

read -rs YNAB_ACCESS_TOKEN

Then register the server:

claude mcp add ynab -e YNAB_ACCESS_TOKEN="$YNAB_ACCESS_TOKEN" -- npx -y ynab-mcp
unset YNAB_ACCESS_TOKEN

This registers the server for you in the current project. Add --scope user to use it everywhere. Claude Code saves the token in its own configuration (~/.claude.json), as Claude Desktop does in its config file. Run claude mcp list to check it connected, or /mcp inside a session.

To add write access later, remove it with claude mcp remove ynab and add it again with -e YNAB_READ_ONLY=false as a second -e option.

Other MCP clients

Any client that launches stdio MCP servers can run ynab-mcp. Give it this command and set YNAB_ACCESS_TOKEN in the server's environment:

npx -y ynab-mcp

Or with Docker (keep -i: stdio needs an open standard input):

docker run -i --rm -e YNAB_ACCESS_TOKEN ghcr.io/auzroz/ynab-mcp:latest

From source

# Clone the repository
git clone https://github.com/auzroz/ynab-mcp.git
cd ynab-mcp

# Install dependencies and build
npm install
npm run build

The server (stdio) needs your token in the environment. Type this command rather than copying it, so your token stays on the clipboard, then paste the token at its prompt, which doesn't echo it or keep it in history (Bash or Zsh; on Windows, WSL or Git Bash):

read -rs YNAB_ACCESS_TOKEN

Then start it:

export YNAB_ACCESS_TOKEN
npm start

To try tools interactively without Claude, use MCP Inspector:

npx @modelcontextprotocol/inspector node dist/index.js

Contributor setup, tests and standards are in CONTRIBUTING.md.

HTTP mode

By default the server speaks stdio to a client on the same machine. Set MCP_TRANSPORT=http to run it as a remote server over MCP's Streamable HTTP transport at POST /mcp, with GET /health for load balancers.

For a quick personal setup, header mode takes your YNAB token per request:

MCP_TRANSPORT=http PORT=3000 YNAB_ACCESS_TOKEN=… npm start
# connect an MCP client via the mcp-remote shim:
npx mcp-remote http://localhost:3000/mcp --header "X-YNAB-Token: <your-token>"

Serve it behind TLS. For claude.ai, for several people, or for anything reachable from the internet, use OAuth mode: see Self-hosting.

Read-only and write mode

ynab-mcp starts read-only. Claude can look at everything but change nothing: write tools are left out of the tool list, and the server refuses them even if a client calls one by name.

To let Claude create, update and delete, add YNAB_READ_ONLY to the server's environment and restart the client:

"env": {
  "YNAB_ACCESS_TOKEN": "your_token_here",
  "YNAB_READ_ONLY": "false"
}
  • Accepted values are true, false, 1, 0, yes and no. Anything else, such as a typo, stops the server at startup rather than guessing.
  • Update and delete tools are marked destructive, because YNAB has no undo. Most clients ask before running them.
  • Every write is recorded in the audit log. Ask Claude to show the audit log to see what changed.

A good habit: keep your everyday setup read-only, and turn writes on when you want Claude to do bookkeeping.

Configuration reference

All settings are environment variables. Most people only need the first one.

All modes

VariableDefaultWhat it does
YNAB_ACCESS_TOKENnoneYour YNAB personal access token. Required for stdio. In HTTP header mode it is the fallback when a request has no X-YNAB-Token.
YNAB_BUDGET_IDlast-usedBudget to use when Claude doesn't name one. A budget UUID, last-used or default.
YNAB_READ_ONLYtrueSet to false to allow writes. See Read-only and write mode.
CACHE_TTL_MS300000How long accounts, categories, payees and the budget list are cached, in milliseconds (5 minutes).
RATE_LIMIT_PER_HOUR180Requests per hour the server allows itself. YNAB's limit is 200; the default keeps a 10% margin.
LOG_LEVELinfodebug, info, warn or error (stdio mode).
MCP_TRANSPORTstdiostdio for a local client, or http for a remote server.

HTTP mode

VariableDefaultWhat it does
PORT3000Port to listen on.
PUBLIC_URLnoneThe public HTTPS address of your server, such as https://ynab-mcp.example.com. Required for OAuth mode, where it is also the OAuth issuer.
MCP_RESOURCE_URLPUBLIC_URL + /mcpThe MCP endpoint's absolute URL, which tokens are bound to.
ALLOWED_HOSTSthe PUBLIC_URL host; localhost too when PUBLIC_URL is unset or loopbackComma-separated Host header allowlist. An explicit list replaces the defaults.
ALLOWED_ORIGINSthe PUBLIC_URL origin; without PUBLIC_URL, https:// for each allowed host, plus http:// for localhostComma-separated Origin allowlist for /mcp. Requests with no Origin header aren't checked.
ENABLE_DNS_REBINDING_PROTECTIONtrueHost and Origin checks. Leave on; behind a proxy, forward Host or set ALLOWED_HOSTS.
TRUST_PROXY0Number of reverse-proxy hops to trust for X-Forwarded-*, such as 1 behind a single Caddy or nginx. Set it only when the server is reachable solely through the proxy (publish no ports), or clients can spoof X-Forwarded-For.
MCP_MAX_SESSIONS100Maximum concurrent MCP sessions.
MCP_MAX_SESSIONS_PER_USER10Maximum sessions per user; past it the user's least recently used session closes. Must not exceed MCP_MAX_SESSIONS.
MCP_SESSION_IDLE_TIMEOUT_MS1800000Idle time before a session closes (30 minutes).

OAuth mode (multi-user)

OAuth mode turns on when YNAB_OAUTH_CLIENT_ID, YNAB_OAUTH_CLIENT_SECRET, ENCRYPTION_KEY and PUBLIC_URL are all set.

VariableDefaultWhat it does
YNAB_OAUTH_CLIENT_IDnoneClient ID of your YNAB OAuth application.
YNAB_OAUTH_CLIENT_SECRETnoneIts client secret.
ENCRYPTION_KEYnone32-byte key that encrypts stored YNAB tokens. Generate one with openssl rand -base64 32 and keep it stable.
YNAB_OAUTH_ALLOW_WRITEtrueOffer read-write at the consent screen. false makes every connection read-only.
STORAGE_DRIVERmemorymemory (lost on restart, for trying it out), sqlite or postgres.
SQLITE_PATHnoneDatabase file for sqlite, such as /data/ynab-mcp.db.
DATABASE_URLnoneConnection string for postgres.
MCP_ACCESS_TOKEN_TTL_SEC3600Lifetime of the access tokens the server issues to MCP clients.
MCP_AUTH_CODE_TTL_SEC600Lifetime of an authorization code and of the consent step.

Numbers must be plain digits (10ms is rejected), and booleans must be one of the accepted words. The server stops with an error that names the variable but never echoes its value.

Troubleshooting

Claude doesn't show the YNAB tools

  • Quit Claude Desktop completely (not just the window) and reopen it.
  • Check the config file is valid JSON: a missing comma or a trailing comma stops every server from loading.
  • Read the server log. On macOS it is in ~/Library/Logs/Claude/ (mcp-server-ynab.log); on Windows in %APPDATA%\Claude\logs\.
  • Check node -v prints 22.14 or later. Claude Desktop may use a different Node.js than your terminal; if in doubt, use the Docker option.

"YNAB_ACCESS_TOKEN is required"

The token didn't reach the server. Make sure it is inside the env block of the "ynab" entry and spelled exactly YNAB_ACCESS_TOKEN. With Docker, both the -e YNAB_ACCESS_TOKEN argument and the env entry are needed.

Every request fails with an authentication error

The token was revoked, mistyped or copied with extra spaces. Create a new one in YNAB's Developer Settings and paste it again.

Claude says it can't add or change anything

That's the read-only default working. See Read-only and write mode.

"Invalid boolean value for YNAB_READ_ONLY"

The value isn't one of true, false, 1, 0, yes or no. The server refuses to start rather than guess whether you meant to allow writes.

Claude is looking at the wrong budget

Without a budget named, the server uses the one you opened most recently in YNAB. Ask Claude to list my budgets and name the one you want, or set YNAB_BUDGET_ID to its ID.

"Rate limit" errors

YNAB allows 200 requests an hour per token, and the server stops at 180. Long analysis sessions can reach it; ask Claude to check the rate limit status to see when it resets. Caching and delta sync keep repeat questions cheap.

HTTP mode answers every request with 403

The Host header isn't on the allowlist, usually because a reverse proxy rewrote it. Forward the original Host, or add the name to ALLOWED_HOSTS (include the public host too, as an explicit list replaces the defaults).

Still stuck?

Ask Claude to run a health check, which tests the connection to YNAB. Then open an issue with the version, how you run it, and what happened. Remove your token, budget names, payees and amounts from anything you paste.