Skip to main content
Glama
rojasgabriel

ynab-mcp-worker

by rojasgabriel

ynab-mcp-worker

A remote MCP server that gives Claude access to your YNAB budget, running on Cloudflare Workers and deployable as a claude.ai custom connector. Once connected it works everywhere you use Claude — web, Desktop, mobile and Cowork — because the connector lives on your Claude account, not on a machine you have to keep running.

Free to run on Cloudflare's free tier. No container, no always-on instance, no monthly bill.

How it holds together

Your YNAB Personal Access Token lives in one place: Cloudflare's encrypted secret store. The Worker reads it to make server-to-server calls to api.ynab.com. It is never sent to Claude and never appears in a log line.

Claude authenticates separately, over OAuth. The first time you add the connector, Claude registers itself, gets sent to a consent screen, and you type your passphrase. After that Claude holds a short-lived access token and a rotating refresh token. Anyone who finds your Worker's URL without the passphrase gets a 401 and nothing else.

  Claude  ──OAuth bearer token──>  this Worker  ──YNAB PAT──>  api.ynab.com
                                        │
                                   AUTH_PASSPHRASE
                                 (typed by you, once)

Be clear-eyed about what that means: the passphrase is the only thing protecting your budget, and the Worker can do anything your YNAB token can do. Use a long random passphrase, and leave YNAB_ALLOW_WRITES off until you have watched the read-only tools behave.

Most of the OAuth machinery is not mine. @cloudflare/workers-oauth-provider is the authorization server: dynamic client registration, PKCE, token issuance, refresh and rotation, and both .well-known metadata documents, with clients and grants stored in a KV namespace and grant props encrypted at rest. This repo supplies two things on top — the consent screen and the MCP endpoint.

Related MCP server: YNAB MCP Server

What Claude can do with it

Six read tools are always available: list_budgets, list_accounts, list_categories, get_month_summary, list_transactions and list_payees.

Three write tools appear only when YNAB_ALLOW_WRITES is "true": create_transaction records a transaction, set_category_budget sets a category's budgeted amount for a month, and move_money shifts budgeted money between two categories — the usual fix for an overspent category.

Amounts crossing the tool boundary are plain currency values like -42.50, never YNAB's internal milliunits, because milliunits are an easy way for a model to be wrong by a factor of a thousand. Responses are trimmed to the fields that matter; raw YNAB categories carry about thirty goal_* fields each, which would bury the numbers you asked about.

Writes are gated twice: the deployment-level YNAB_ALLOW_WRITES switch, and the OAuth scope actually granted to the token. A connection authorized for ynab:read alone will not even see the write tools listed.

Deploying

You need a YNAB Personal Access Token (YNAB → Account Settings → Developer Settings → New Token) and a free Cloudflare account.

Generate a passphrase first and put it somewhere you will not lose it:

openssl rand -base64 24

Then pick one of three routes. All three end up in the same place.

Option 1 — one click, no terminal

Push this repo to GitHub (public is fine — there are no secrets in it), then replace YOUR-USERNAME/YOUR-REPO below and click the button:

Deploy to Cloudflare

Cloudflare clones the repo into your account, creates the KV namespace and writes its id into the config for you, prompts for the two secrets from .dev.vars.example, deploys, and wires up Workers Builds so future pushes redeploy automatically. It works in a phone browser.

Option 2 — connect a GitHub repo in the dashboard

If the repo is already on GitHub and you would rather not use a button:

  1. Create the KV namespace: Cloudflare dashboard → Storage & Databases → KV → Create. Paste the id into wrangler.jsonc, commit, push.

  2. Workers & Pages → Create application → Import a repository → pick it.

  3. Once it deploys, open the Worker → Settings → Variables and Secrets, and add YNAB_ACCESS_TOKEN and AUTH_PASSPHRASE as secrets (not plaintext variables). Redeploy.

Every push to main redeploys from then on. Also browser-only.

Option 3 — the CLI

npm install
npx wrangler login

# Creates the KV namespace the OAuth provider uses; paste the returned id
# into wrangler.jsonc
npx wrangler kv namespace create OAUTH_KV

# Pick a name in wrangler.jsonc — it becomes part of your URL

# Stored encrypted by Cloudflare; never in the repo, never in the bundle
npx wrangler secret put YNAB_ACCESS_TOKEN
npx wrangler secret put AUTH_PASSPHRASE

npx wrangler deploy

Wrangler prints your URL, something like https://ynab-mcp.your-subdomain.workers.dev.

A note on GitHub Actions

.github/workflows/deploy.yml is included for deploying from CI instead, which also runs a typecheck first. It needs one repo secret, CLOUDFLARE_API_TOKEN. If you used Option 1 or 2, Cloudflare already deploys on push and this workflow is redundant — delete it.

Either way, do not put YNAB_ACCESS_TOKEN or AUTH_PASSPHRASE into GitHub. Worker secrets persist across deploys, so CI never needs them, and keeping them out means one fewer place your YNAB token exists.

Turning on writes

Set YNAB_ALLOW_WRITES to "true" in wrangler.jsonc and redeploy (push, or npx wrangler deploy). You will also need to disconnect and reconnect the connector in Claude, because the granted scope is baked into the existing token.

Running it locally first

cp .dev.vars.example .dev.vars   # fill in the two secrets
npx wrangler dev

wrangler dev simulates KV locally, so the placeholder namespace id in wrangler.jsonc is fine for local runs. Claude cannot reach localhost, so this is for verification only — see the smoke test below.

Connecting it to Claude

In claude.ai, open Settings → Connectors → Add custom connector, and give it:

https://your-worker-url.workers.dev/mcp

Note the /mcp path. The bare origin will not work: the protected resource metadata declares the resource identifier as the full URL including the path, and Claude checks that the two match.

Leave the OAuth Client ID and Secret fields empty — the Worker supports Dynamic Client Registration, so Claude registers itself. Claude then opens the consent screen; type your passphrase and approve. That is the whole setup, and it works from a phone browser if you would rather not use a laptop.

Your Worker needs to be reachable from Anthropic's egress range, 160.79.104.0/21. Workers are public by default, so this only matters if you put Cloudflare Access or a WAF rule in front of it.

Verifying a deployment

scripts/smoke-test.mjs walks the exact path Claude walks — discovery, dynamic client registration, the consent screen, the PKCE token exchange, an authenticated MCP session, refresh rotation — and checks that the things that should be rejected are: unauthenticated calls, forged tokens, wrong passphrases, mismatched PKCE verifiers, replayed authorization codes and superseded refresh tokens.

node scripts/smoke-test.mjs https://your-worker-url.workers.dev 'your-passphrase'

It exits non-zero if anything fails. Run it after your first deploy, and again any time you change the auth configuration.

scripts/check-behaviors.mjs is a smaller companion that prints which tools a given OAuth scope actually exposes — the quickest way to confirm read-only mode is doing what you think.

Configuration

Set in wrangler.jsonc under vars:

Variable

Default

Purpose

YNAB_ALLOW_WRITES

"false"

"true" registers the write tools.

YNAB_DEFAULT_BUDGET_ID

"last-used"

Budget used when a tool call omits one.

SERVER_LABEL

"YNAB MCP"

Name shown on the consent screen.

Set with wrangler secret put, never in the repo:

Secret

Purpose

YNAB_ACCESS_TOKEN

Your YNAB Personal Access Token.

AUTH_PASSPHRASE

Typed on the consent screen. Make it long and random.

What this costs

Nothing, in practice. Cloudflare's Workers free tier covers 100,000 requests a day and the KV free tier covers 100,000 reads and 1,000 writes a day. A personal connector makes a handful of requests per conversation, and KV writes only happen when a client registers or a token rotates. YNAB's own limit of 200 requests per hour per token will bind long before anything of Cloudflare's does.

Security notes, including one honest caveat

The passphrase is hashed and compared in constant time, so neither its length nor its content leaks through timing. The consent page ships a strict CSP, sets X-Frame-Options: DENY, and runs no JavaScript at all.

The parsed authorization request is round-tripped through a hidden form field. That is safe because completeAuthorization independently re-validates the client, the redirect URI against the client's registered URIs, the PKCE challenge and the resource parameter — a tampered field is rejected rather than followed.

The caveat. Cloudflare's OAuth provider rotates refresh tokens on every use but deliberately keeps exactly one previous token valid, so that a client whose rotation response was lost in flight is not permanently locked out. It does not implement strict reuse detection, which would revoke the whole grant the moment a superseded token is replayed. This is a mainstream trade-off — Auth0 and Okta both ship the same grace window — but it is weaker than revoking on replay, and it is worth knowing rather than assuming. A token from further back than that one step is refused; the smoke test pins exactly that boundary.

Rotation behaves differently per secret, and one case is a trap. Rotating the YNAB token is invisible to Claude — set it and redeploy. Rotating AUTH_PASSPHRASE does not disconnect existing connections, because they already hold refresh tokens and never see the consent screen again. If you think your passphrase leaked, change it and revoke the existing grants by clearing the KV namespace, then reconnect.

Licence

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to interact with YNAB budgets through natural language. Supports managing accounts, categories, transactions, and budget months with 21 tools for comprehensive budget operations.
    -
  • F
    license
    A
    quality
    C
    maintenance
    Enables users to manage budgets, accounts, categories, and transactions on You Need A Budget (YNAB) through Claude. It supports both core daily budget management and extended operations like bulk transaction creation and historical trend analysis.
    15
    2
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI assistants to interact with YNAB budgets, performing read-only queries by default and optional write operations like creating transactions and managing categories through natural language.
    39
    223 npm
    33
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables reading and writing YNAB budget data, such as listing budgets, accounts, categories, transactions, and creating or updating transactions, through natural language commands.
    8
    MIT