Skip to main content
Glama
lukendatigh

ynab-mcp-server

README.md
# ynab-mcp-server

A complete, open-source single tenant [MCP](https://modelcontextprotocol.io) server for the full [YNAB](https://www.ynab.com) v1 API. Every endpoint in YNAB's [OpenAPI spec](https://api.ynab.com/papi/open_api_spec.yaml) is reachable through one of the tools below -- including payee locations, category groups, user info, plan settings, and account creation, which most other YNAB MCP servers skip.

Runs two ways:
- **Locally over stdio** -- for Claude Desktop / Claude Code, zero hosting required.
- **Remotely over HTTP** -- for [claude.ai](https://claude.ai) / Cowork connectors, deployable to Fly.io with the included Dockerfile.

View the full product and technical design [here](PRD).

## Tools

23 tools cover all 30 YNAB endpoint paths (several endpoints are consolidated behind one tool via a `view`/`action` parameter to avoid near-duplicate tools). Full list with exact endpoint coverage: [docs/tools.md](docs/tools.md).

| Read | Write |
|---|---|
| `get_user` | `create_account` |
| `list_plans`, `get_plan`, `get_plan_settings` | `create_category`, `update_category` |
| `get_budget` | `modify_budget` |
| `list_accounts`, `get_account` | `create_category_group`, `update_category_group` |
| `list_category_groups`, `get_category_group` | `create_payee`, `update_payee` |
| `get_transactions` | `modify_transactions` |
| `list_payees` | `modify_scheduled_transactions` |
| `list_payee_locations`, `get_payee_location` | |

## Quick start (local, stdio)

Requires Node.js 20+. Grab a [YNAB personal access token](https://app.ynab.com/settings/developer) first.

```bash
npx ynab-mcp-server
```

Point your MCP client (Claude Desktop, Claude Code, etc.) at it. For Claude Desktop, add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "ynab": {
      "command": "npx",
      "args": ["ynab-mcp-server"],
      "env": {
        "YNAB_ACCESS_TOKEN": "your-token-here",
        "YNAB_READ_ONLY": "false"
      }
    }
  }
}
```

## Configuration

| Env var | Required | Default | Description |
|---|---|---|---|
| `YNAB_ACCESS_TOKEN` | yes | -- | Your YNAB personal access token. Never logged, never echoed in tool output. |
| `YNAB_READ_ONLY` | no | `false` | When `true`, every mutation tool is hidden from `tools/list` entirely. Recommended for any instance you don't fully trust the client of. |
| `YNAB_API_BASE_URL` | no | `https://api.ynab.com/v1` | Override for testing against a mock server. |

HTTP mode (below) needs three more: `PORT`, `PUBLIC_URL`, `MCP_HTTP_PASSWORD`. See [.env.example](.env.example) for all of them with descriptions.

## Remote (HTTP) deployment on Fly.io

The HTTP transport is gated by OAuth (required for claude.ai/Cowork connector approval). This is **single-tenant OAuth**: there's no concept of separate user accounts, it just gates access to *your* instance behind one shared password. See [docs/architecture.md](docs/architecture.md#oauth) for why and how.

1. Install [flyctl](https://fly.io/docs/flyctl/install/) and `fly auth login`.
2. `fly launch --no-deploy` from this directory (it will read `fly.toml`; rename the `app` there first if you want a specific subdomain).
3. Set secrets (never put these in `fly.toml`, which is committed to git):
   ```bash
   fly secrets set YNAB_ACCESS_TOKEN=your-token-here
   fly secrets set MCP_HTTP_PASSWORD=choose-a-strong-password
   ```
4. Edit `fly.toml`'s `PUBLIC_URL` to match your actual `*.fly.dev` hostname (or custom domain), then:
   ```bash
   fly deploy
   ```
5. In claude.ai / Cowork, add a custom connector pointing at `https://<your-app>.fly.dev/mcp`. You'll be redirected to a login page on your own instance -- enter `MCP_HTTP_PASSWORD` to approve the connection.

Notes:
- Single `shared-cpu-1x` machine, no volume. OAuth session state (registered clients, tokens) lives in memory and is lost on redeploy or restart -- you'll just need to reconnect the connector afterwards. Acceptable trade-off for a low-traffic personal instance; see the TDD.
- `fly.toml`'s `[http_service]` is configured to stay always-on (not scaled to zero) for exactly that reason -- a stop/start cycle would otherwise force reconnection too.

## Development

```bash
npm install
npm run dev:stdio      # run src/stdio.ts directly with tsx
npm run dev:http       # run src/http.ts directly with tsx
npm run typecheck
npm run lint            # biome check
npm run lint:fix
npm test
npm run build           # bundles dist/stdio.js and dist/http.js with tsup
```

See [docs/architecture.md](docs/architecture.md) for the repo layout and request-flow details.

## License

MIT -- see [LICENSE](LICENSE).