CLAUDE.md•5.38 kB
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
YNAB Assistant is a FastMCP (Model Context Protocol) server that provides AI-accessible tools for interacting with the YNAB (You Need A Budget) API. The server exposes 21 tools via HTTP transport for managing budgets, accounts, categories, months, and transactions.
## Development Commands
### Running the Server
```bash
uv run python main.py
```
Server runs on HTTP transport at `http://localhost:8000/mcp`
### Linting and Type Checking
**CRITICAL**: Always run both linters iteratively until all issues are resolved:
```bash
ruff check --fix && basedpyright
```
Continue running this command in a loop, fixing issues each iteration, until both pass cleanly.
### Environment Setup
Required environment variable:
```bash
export YNAB_ACCESS_TOKEN="your_token_here"
```
Use `.envrc` with direnv for local development.
## Architecture
### Modular Structure
The codebase follows a **DRY (Don't Repeat Yourself)** principle with shared authentication:
```
main.py # FastMCP server setup, tool registration
├── auth.py # Shared authentication helpers (get_api_configuration, get_api_client)
├── accounts.py # Account discovery tools (2 tools)
├── categories.py # Category management tools (5 tools)
├── months.py # Month management tools (2 tools)
└── transactions.py # Transaction management tools (11 tools)
```
### Tool Registration Pattern
All tools from module files are imported and registered in `main.py` using:
```python
from accounts import get_accounts, get_account_by_id
mcp.tool(get_accounts)
mcp.tool(get_account_by_id)
```
### Authentication Flow
All API interactions follow this pattern:
1. Call `get_api_configuration()` from `auth.py` (reads YNAB_ACCESS_TOKEN)
2. Call `get_api_client(configuration)` to get authenticated client
3. Use client in context manager (`with` statement)
4. Return data via `model_dump()` for consistent dict responses
### YNAB SDK Type Issues
The `ynab-sdk-python` package has incomplete type definitions. Common patterns:
**User/Transaction objects lack field definitions:**
```python
user = api_response.data.user # type: ignore[attr-defined]
user_dict = user.model_dump() # type: ignore[attr-defined]
```
**Date parameters accept strings at runtime but typed as `date`:**
```python
since_date=since_date # type: ignore[arg-type]
```
**Wrapper objects accept dicts but typed for specific models:**
```python
wrapper = ynab.PostTransactionsWrapper(transaction=transaction_data) # type: ignore[arg-type]
```
These `# type: ignore` comments are necessary and correct.
### Default Parameters Convention
All tools that require a `budget_id` default to `"last-used"`:
```python
def get_transactions(budget_id: str = "last-used", ...):
```
This uses YNAB's "last-used" budget feature for convenience.
### Amount Representation
YNAB uses **milliunits** for all monetary amounts:
- `-50000` = -$50.00
- `125000` = $125.00
## Tool Categories
### User Tools (1)
- `get_user()` - Get authenticated user info
### Account Tools (2)
- `get_accounts()` - List all accounts (use this to discover account IDs)
- `get_account_by_id(account_id)`
### Category Tools (5)
**Read operations (3):**
- `get_categories(budget_id, last_knowledge_of_server)` - List all categories grouped by category group
- `get_category_by_id(category_id, budget_id)` - Get single category with current month amounts
- `get_month_category_by_id(category_id, month, budget_id)` - Get category for specific month
**Write operations (2):**
- `update_category(category_id, budget_id, note, budgeted)` - Update category note and/or budgeted amount
- `update_month_category(category_id, month, budgeted, budget_id)` - Update budgeted amount for specific month
### Month Tools (2)
**Read operations (2):**
- `get_budget_months(budget_id, last_knowledge_of_server)` - List all budget months
- `get_budget_month(month, budget_id)` - Get single budget month with categories and category groups
### Transaction Tools (11)
**Read operations (6):**
- `get_transaction_by_id(transaction_id)`
- `get_transactions(budget_id, since_date)`
- `get_transactions_by_account(account_id, budget_id, since_date)`
- `get_transactions_by_category(category_id, budget_id, since_date)`
- `get_transactions_by_month(month, budget_id)` - month: "2024-12-01" or "current"
- `get_transactions_by_payee(payee_id, budget_id, since_date)`
**Write operations (5):**
- `create_transaction(account_id, date, amount, ...)`
- `create_transactions(transactions, budget_id)` - bulk create
- `update_transaction(transaction_id, ...)`
- `update_transactions(transactions, budget_id)` - bulk update
- `delete_transaction(transaction_id)`
## Code Quality Standards
### Linting Configuration
Ruff is configured with strict rules including:
- E/W (pycodestyle), F (pyflakes), I (isort)
- B (bugbear), UP (pyupgrade), SIM (simplify)
- DOC (pydoclint), D (pydocstyle)
- PERF (performance), ARG (unused args)
**Max cyclomatic complexity: 10**
### Type Checking
Pyright runs in **strict mode** (`typeCheckingMode = "strict"`). All functions must have:
- Full type hints for parameters
- Return type annotations
- Proper docstrings with Args, Returns, and Raises sections
### Python Version
Requires Python 3.14+