loading…
Search for a command to run...
loading…
Read-only MCP server for YNAB that provides tools for budgets, accounts, categories, transactions, and financial summaries via HTTP or stdio.
Read-only MCP server for YNAB that provides tools for budgets, accounts, categories, transactions, and financial summaries via HTTP or stdio.
ynab-mcp-bridge is a read-only Model Context Protocol (MCP) server for YNAB.
It gives MCP clients a shared YNAB backend over either:
stdio for local desktop clients and debuggingPOST /mcp handling that works well with clients that do not keep durable MCP sessionsYNAB_PLAN_ID is not set| 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.
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 under dist/.dist/ artifacts in sync with the source change rather than treating them as disposable local-only files.verify:build-sync after npm run build, and it also runs verify:pack to smoke-test the packed npm artifact before treating a build as release-ready.export YNAB_API_TOKEN=your-token
npm start
Defaults:
127.0.0.13000/mcpstdioexport YNAB_API_TOKEN=your-token
npm run start:stdio
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.
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
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_PATHLegacy MCP_OAUTH_* settings are still supported as compatibility overrides, but they are no longer required for the primary auth2 HTTP path.
| 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.
| 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:
Origin header is present, HTTP mode validates it.oauth-hardened refuses to start without MCP_ALLOWED_ORIGINS.| 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 |
POST /mcp and should not depend on durable MCP sessions.Mcp-Session-Id headers for compatibility, but session continuity is not the primary path./.well-known/oauth-authorization-server/register/authorize/token/.well-known/oauth-protected-resource/mcpMCP_OAUTH_SKIP_LOCAL_CONSENT=true is intended for owner-operated deployments where you want the bridge to forward every authorization request upstream without showing the local approval page.For Cloudflare Access, the simplest setup is:
MCP_PUBLIC_URLMCP_AUTH2_CONFIG_PATHPut 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:
MCP_PUBLIC_URL and MCP_OAUTH_CALLBACK_PATH, for example https://mcp.example.com/oauth/callbackMCP_PUBLIC_URL on the external HTTPS hostname, not the internal bind address/cdn-cgi/access/sso/oauth2/* endpoints for this flowIf Cloudflare injects Cf-Access-Jwt-Assertion, the bridge can translate that assertion into a bridge-local token as an explicit compatibility path.
The server exposes a read-only YNAB toolset across:
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.
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:
--urlinitialize, tools/list, and ynab_get_mcp_version--max-error-rateUseful 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 --url is 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 timeRecommended workflow:
smoke on local changesbaseline artifacts on a stable environmentstress and spike before higher-risk releasessoak on a scheduled cadence or before major rollout eventsThresholds 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.
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
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.
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.
Выполни в терминале:
claude mcp add ynab-mcp-bridge -- npx Безопасность
Низкий рискАвтоматическая эвристика по публичным данным — не гарантия безопасности.