Ynab Bridge
FreeNot checkedRead-only MCP server for YNAB that provides tools for budgets, accounts, categories, transactions, and financial summaries via HTTP or stdio.
About
Read-only MCP server for YNAB that provides tools for budgets, accounts, categories, transactions, and financial summaries via HTTP or stdio.
README
ynab-mcp-bridge is a read-only Model Context Protocol (MCP) server for YNAB.
It gives MCP clients a shared YNAB backend over either:
- stateless HTTP for self-hosted or remote deployments
- optional OAuth-protected HTTP for remote browser-based MCP clients
stdiofor local desktop clients and debugging
What You Get
- Read-only YNAB tools for plans, accounts, categories, payees, transactions, scheduled transactions, and summary views
- HTTP mode by default
- Stateless
POST /mcphandling that works well with clients that do not keep durable MCP sessions - Optional auth2-backed OAuth mode for remote clients such as Claude Web
- Automatic plan resolution when
YNAB_PLAN_IDis not set
Choose A Mode
| Mode | Use it when | Auth | Key settings |
|---|---|---|---|
http + authless |
Local use or a trusted self-hosted setup | None | YNAB_API_TOKEN |
http + oauth-single-tenant |
Remote client access behind an upstream IdP | MCP-side OAuth | MCP_PUBLIC_URL and MCP_AUTH2_CONFIG_PATH |
http + oauth-hardened |
Same as above, but fail closed unless origins are explicitly allowed | MCP-side OAuth | MCP_PUBLIC_URL, MCP_AUTH2_CONFIG_PATH, and MCP_ALLOWED_ORIGINS |
stdio |
Local desktop clients and debugging | None | YNAB_API_TOKEN |
authless is the default deployment mode. http is the default transport.
Quick Start
1. Install and build
npm install
npm run build
npm run pr:ci
Build artifact policy:
dist/remains tracked in this repository for now because the published package and CLI entrypoints resolve from built JavaScript underdist/.- When a source change affects runtime output, keep the generated
dist/artifacts in sync with the source change rather than treating them as disposable local-only files. - CI enforces that policy with
verify:build-syncafternpm run build, and it also runsverify:packto smoke-test the packed npm artifact before treating a build as release-ready. - When CI passes on the exact release commit and your dependency lockfile is already installed locally, pulling the release should be a restart-first path rather than a rebuild-first path.
2. Run the default local HTTP server
export YNAB_API_TOKEN=your-token
npm start
Defaults:
- host:
127.0.0.1 - port:
3000 - path:
/mcp
3. Run over stdio
export YNAB_API_TOKEN=your-token
npm run start:stdio
Local CI Preflight
Run this before creating a PR when you want one command that mirrors the required CI gates instead of running individual test commands:
npm run pr:ci
pr:ci runs the required local checks from CI: test:ci, test:coverage, lint:deps, lint, typecheck, lint:unused, and build. It intentionally does not include the advisory-only lint:oxlint step because that CI job is non-blocking.
preflight remains available as a backward-compatible alias, but the default pre-PR workflow should be npm run pr:ci.
For advisory quality reporting outside the blocking preflight gate, you can also run:
npm run lint:duplicates
npm run tech-debt:report
lint:duplicates runs JSCPD across maintained production, tooling, and configuration files using the checked-in .jscpd.json settings. It excludes generated output, Markdown, specs, contracts, and local task docs, and it also ignores the auth2 harness so duplication reporting stays focused on the production module surface. tech-debt:report uses the same advisory boundary and prints the current duplication, dead-export, suppression, debt-marker, and dependency-update counts.
4. Expose authless HTTP intentionally
Use this only when the network path is already trusted.
MCP_TRANSPORT=http \
MCP_HOST=0.0.0.0 \
MCP_ALLOWED_ORIGINS=https://claude.ai \
YNAB_API_TOKEN=your-token \
npm run start:http
5. Enable OAuth for a remote client
This is the recommended remote setup.
MCP_TRANSPORT=http \
MCP_HOST=0.0.0.0 \
MCP_ALLOWED_ORIGINS=https://claude.ai \
MCP_DEPLOYMENT_MODE=oauth-single-tenant \
MCP_PUBLIC_URL=https://mcp.example.com/mcp \
MCP_AUTH2_CONFIG_PATH=./auth2.config.example.json \
YNAB_API_TOKEN=your-token \
npm run start:http
The checked-in auth2.config.example.json shows the canonical auth2 file shape loaded through MCP_AUTH2_CONFIG_PATH. Copy it to a private path, replace the placeholder provider values, and keep /oauth/callback as the callback path.
With an auth2 config file in place, the minimal remote OAuth env surface is:
MCP_PUBLIC_URLMCP_AUTH2_CONFIG_PATH
Legacy MCP_OAUTH_* settings are still supported as compatibility overrides, but they are no longer required for the primary auth2 HTTP path.
Environment Variables
YNAB Backend
| Variable | Required | Notes |
|---|---|---|
YNAB_API_TOKEN |
Yes | Shared backend YNAB token |
YNAB_PLAN_ID |
No | Default plan for tools that accept planId |
If YNAB_PLAN_ID is unset, the bridge tries YNAB's default_plan first, then the only available plan when exactly one exists. If a configured plan becomes stale, the bridge retries once with a fresh resolution.
Transport and HTTP
| Variable | Default | Notes |
|---|---|---|
MCP_TRANSPORT |
http |
http or stdio |
MCP_HOST |
127.0.0.1 |
HTTP only |
MCP_PORT |
3000 |
HTTP only |
MCP_PATH |
/mcp |
HTTP only |
MCP_ALLOWED_ORIGINS |
empty | Comma-separated browser origin allowlist |
MCP_ALLOWED_HOSTS |
empty | Optional comma-separated Host header allowlist |
MCP_DEPLOYMENT_MODE |
authless |
authless, oauth-single-tenant, or oauth-hardened |
MCP_AUTH_MODE |
none | Legacy compatibility shim: none or oauth |
Notes:
- When an
Originheader is present, HTTP mode validates it. - Loopback origins are allowed automatically for loopback hosts.
oauth-hardenedrefuses to start withoutMCP_ALLOWED_ORIGINS.
OAuth
| Variable | Required | Notes |
|---|---|---|
MCP_PUBLIC_URL |
Yes in OAuth modes | Public MCP URL, for example https://mcp.example.com/mcp |
MCP_AUTH2_CONFIG_PATH |
Yes in OAuth modes | Path to the auth2 JSON config file |
MCP_OAUTH_AUDIENCE |
No | Defaults to MCP_PUBLIC_URL |
MCP_OAUTH_STORE_PATH |
No | Defaults to ~/.ynab-mcp-bridge/oauth-store.json |
MCP_OAUTH_TOKEN_SIGNING_SECRET |
No | Defaults to a stable derived secret |
MCP_OAUTH_CALLBACK_PATH |
No | Defaults to /oauth/callback |
MCP_OAUTH_SKIP_LOCAL_CONSENT |
No | Set to true to bypass the bridge-hosted approval screen for every OAuth client |
MCP_OAUTH_SCOPES |
No | Comma-separated runtime scope override; defaults to the auth2 config scopes |
MCP_OAUTH_CLOUDFLARE_DOMAIN |
Optional compatibility override | Shortcut for Cloudflare Access endpoint derivation |
MCP_OAUTH_ISSUER |
Optional compatibility override | Upstream issuer |
MCP_OAUTH_AUTHORIZATION_URL |
Optional compatibility override | Upstream authorization endpoint |
MCP_OAUTH_TOKEN_URL |
Optional compatibility override | Upstream token endpoint |
MCP_OAUTH_JWKS_URL |
Optional compatibility override | Upstream JWKS endpoint |
MCP_OAUTH_CLIENT_ID |
Optional compatibility override | Upstream confidential client ID |
MCP_OAUTH_CLIENT_SECRET |
Optional compatibility override | Upstream confidential client secret |
How HTTP Mode Behaves
- The default HTTP transport is stateless. Clients should send
POST /mcpand should not depend on durable MCP sessions. - The bridge still exposes
Mcp-Session-Idheaders for compatibility, but session continuity is not the primary path. - In OAuth modes, the bridge acts as the MCP authorization server and exposes:
/.well-known/oauth-authorization-server/register/authorize/token/.well-known/oauth-protected-resource/mcp
- OAuth here protects access to a shared backend YNAB token. It does not do per-user YNAB OAuth delegation.
MCP_OAUTH_SKIP_LOCAL_CONSENT=trueis intended for owner-operated deployments where you want the bridge to forward every authorization request upstream without showing the local approval page.
Cloudflare Access
For Cloudflare Access, the simplest setup is:
MCP_PUBLIC_URLMCP_AUTH2_CONFIG_PATH
Put the Cloudflare issuer, authorization endpoint, token endpoint, JWKS URI, client ID, and client secret in the auth2 config file. The legacy MCP_OAUTH_* settings remain available if you need to override those values at runtime.
Important details:
- Register the callback built from
MCP_PUBLIC_URLandMCP_OAUTH_CALLBACK_PATH, for examplehttps://mcp.example.com/oauth/callback - Keep
MCP_PUBLIC_URLon the external HTTPS hostname, not the internal bind address - Use the public MCP URL as the audience unless your Access app expects a different resource identifier
- Do not use the older tenant-wide
/cdn-cgi/access/sso/oauth2/*endpoints for this flow
If Cloudflare injects Cf-Access-Jwt-Assertion, the bridge can translate that assertion into a bridge-local token as an explicit compatibility path.
Tool Coverage
The server exposes a read-only YNAB toolset across:
- user and plan metadata
- plan settings and plan months
- accounts, categories, and payees
- transactions and scheduled transactions
- payee locations
- money movement and transfer summaries
- higher-level financial summaries such as spending, cash flow, income, goal progress, obligations, and budget health
For YNAB-style summaries, treat assigned_vs_spent as a timing and buffering signal, not a score for budget discipline. In buffered budgets it often reflects paycheck timing, category staging, or money reserved for future months rather than overspending or underspending by itself.
CLI Examples
Start with the default HTTP settings:
node dist/index.js
Start over stdio:
node dist/index.js --transport stdio
Start over HTTP explicitly:
node dist/index.js --transport http --host 127.0.0.1 --port 3000 --path /mcp
Run the local HTTP reliability probe:
npm run reliability:http -- --requests 10 --concurrency 2
The reliability command uses a bounded authless HTTP scenario that:
- starts a local bridge unless you pass
--url - runs
initialize,tools/list, andynab_get_mcp_version - prints attempts, failures, error rate, and latency percentiles
- exits non-zero when the error rate is above
--max-error-rate
Useful flags:
--requests <n>: number of reliability sequences to run. Each sequence performs three MCP operations.--concurrency <n>: number of sequences to run in parallel.--max-error-rate <0..1>: fail the run if the observed error rate exceeds this threshold.--url <http-url>: target an already running bridge instead of starting a local one.--host,--port,--path: override the local server bind address when--urlis not used.
Write a machine-readable smoke artifact and compare against a prior run:
npm run reliability:http -- \
--requests 10 \
--concurrency 2 \
--json-out artifacts/reliability/smoke.json \
--baseline-artifact artifacts/reliability/baseline-smoke.json
Run the heavier reliability suite in dry-run mode:
npm run reliability:load -- \
--profile baseline \
--url http://127.0.0.1:3000/mcp \
--json-out artifacts/reliability/baseline.json \
--dry-run
The dedicated load suite is designed for named profiles instead of ad hoc request counts:
smoke: fast local regression check using the built-in Node probebaseline: repeatable average-load run for comparisonsstress: higher sustained load to expose overload behaviorspike: sudden burst behaviorsoak: longer steady-state run to catch degradation over time
Recommended workflow:
- run
smokeon local changes - record
baselineartifacts on a stable environment - run
stressandspikebefore higher-risk releases - run
soakon a scheduled cadence or before major rollout events
Thresholds should be evaluated with error rate and latency percentiles such as p95 and p99, not averages alone. The smoke command and artifact comparison flow already follow that model, and the load-suite dry run prints the exact thresholds that would be enforced by the heavier external runner.
Allow specific browser origins:
node dist/index.js \
--transport http \
--host 0.0.0.0 \
--port 3000 \
--path /mcp \
--allowed-origins https://claude.ai,https://chat.openai.com
Lock down accepted host headers too:
node dist/index.js \
--transport http \
--host 0.0.0.0 \
--port 3000 \
--path /mcp \
--allowed-origins https://claude.ai \
--allowed-hosts mcp.example.com
Enable OAuth with explicit upstream endpoints:
node dist/index.js \
--transport http \
--host 0.0.0.0 \
--port 3000 \
--path /mcp \
--allowed-origins https://claude.ai \
--deployment-mode oauth-single-tenant \
--public-url https://mcp.example.com/mcp \
--oauth-issuer https://example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/client-123 \
--oauth-authorization-url https://example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/client-123/authorization \
--oauth-token-url https://example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/client-123/token \
--oauth-jwks-url https://example.cloudflareaccess.com/cdn-cgi/access/sso/oidc/client-123/jwks \
--oauth-client-id cloudflare-access-client-id \
--oauth-client-secret cloudflare-access-client-secret \
--oauth-audience https://mcp.example.com/mcp \
--oauth-store-path /var/lib/ynab-mcp-bridge/oauth-store.json \
--oauth-token-signing-secret replace-with-a-long-random-secret \
--oauth-scopes openid,profile
For a stricter remote deployment, switch oauth-single-tenant to oauth-hardened.
Docker
Build the image:
docker build -t ynab-mcp-bridge .
Run the default HTTP server:
docker run --rm \
-p 3000:3000 \
-e YNAB_API_TOKEN=your-token \
ynab-mcp-bridge
Rate Limiting
YNAB documents a limit of 200 requests per rolling hour per access token. The bridge applies a shared per-token sliding-window limiter and retries 429 Too Many Requests responses conservatively.
Development
npm test
npm run build
npm run lint:duplicates
npm run tech-debt:report
lint:duplicates runs a JSCPD baseline across maintained production, tooling, and configuration files. It excludes generated/vendor paths, Markdown, specs, contracts, local task docs, and the auth2 harness so the duplicate baseline tracks the production-facing codebase.
tech-debt:report prints the current advisory duplicate-remediation baseline together with dead-export and suppression counts so local cleanup work has one repeatable snapshot command.
Install Ynab Bridge in Claude Desktop, Claude Code & Cursor
unyly install ynab-mcp-bridgeInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add ynab-mcp-bridge -- npx -y github:mossipcams/ynab-mcp-bridgeFAQ
Is Ynab Bridge MCP free?
Yes, Ynab Bridge MCP is free — one-click install via Unyly at no cost.
Does Ynab Bridge need an API key?
No, Ynab Bridge runs without API keys or environment variables.
Is Ynab Bridge hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Ynab Bridge in Claude Desktop, Claude Code or Cursor?
Open Ynab Bridge on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectCompare Ynab Bridge with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
