budget-mcp
# ๐ฐ Personal Budget MCP Server
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
<!-- Add once GitHub Actions is set up:
[](https://github.com/PedroLiu1999/budget-mcp/actions/workflows/ci.yml)
-->
**Track personal finances by talking to an LLM.** An MCP server that gives any MCP-compatible client typed tools to log transactions, manage a category library, and analyse spending โ plus interactive dashboards rendered directly in the chat client.
Built with Python, `FastMCP`, SQLAlchemy, SQLite and PostgreSQL.
---
## Why this exists
Chat is a good interface for expense logging. "Spent ยฃ42 at Tesco and ยฃ8 on coffee" is faster than opening an app and filling in two forms, and an LLM can categorise it for you.
The problem is that an LLM with no tools will happily *tell* you it logged your transaction. Getting this to work means the model must be unable to confuse "I recorded this" with "I described recording this" โ so the tools have to return unambiguous success or failure, reject bad input rather than coercing it, and expose enough query surface that the agent reads real state instead of reconstructing it from conversation history.
That design problem is the actual point of this repo. The budgeting is the excuse.
---
## ๐ธ Screenshots
| Budget dashboard | Spending trends |
| :--- | :--- |
|  |  |
---
## ๐ง Design notes: making agent calls trustworthy
<!-- TODO: verify each claim below against the current implementation before publishing.
Delete anything not actually true โ an unsupported claim here is worse than no section. -->
**Explicit failure over silent coercion.** Tools validate input and return a structured error naming what was wrong, rather than guessing at intent. An invalid `type`, a malformed date, or a `category_id` that doesn't exist fails loudly, so the agent can correct itself and report accurately to the user instead of inventing a confirmation.
**Batch-first write tools.** `add_transaction`, `update_transaction`, `delete_transaction` and `add_category` all accept either a single item or an `items` list. Agents naturally handle several things at once ("log these five expenses"), and forcing them into one call per record multiplies both latency and the number of places a partial failure can hide.
**Referential integrity at the tool boundary.** `delete_category` takes an optional `reassign_to_category_id`, so removing a category can't silently orphan its transactions. The integrity decision is surfaced as a parameter the agent must reason about rather than a side effect it discovers later.
**Read surface sized for multi-turn use.** `get_summary`, `get_transactions` and `get_uncategorized_transactions` cover aggregate, detail and triage reads with consistent filter arguments across all three. `get_uncategorized_transactions` returns results sorted by description specifically so an agent can categorise bulk imports in coherent groups instead of one row at a time.
**Idempotent schema bootstrap.** Tables and 15 default categories are created on first startup, so a fresh clone or a new cloud deployment is immediately usable and there's no partially-initialised state for a tool call to land in.
---
## โจ Features
- **Local or cloud storage** โ zero-setup SQLite (in-memory or `data/budget.db`), or PostgreSQL via any provider such as Neon.
- **Interactive dashboards in-client** โ category pie charts and searchable transaction tables rendered via `prefab-ui`, returned as MCP UI apps rather than plain text.
- **Spending trends** โ continuous category line chart with month/week/day granularity toggle, date-range slider and searchable table.
- **Batch operations** โ single-item or bulk write across transactions and categories.
- **Reproducible environment** โ `uv` for fast, locked dependency resolution.
- **Broad client support** โ Claude Desktop, Claude Code, Cursor, Goose, Open WebUI, and any other MCP host.
---
## ๐ Quickstart
```bash
git clone https://github.com/PedroLiu1999/budget-mcp.git
cd budget-mcp
uv sync
uv run pytest # confirm the install works
uv run server.py # start the server (in-memory SQLite by default)
```
Then register it with your client โ Claude Code is the one-liner:
```bash
claude mcp add budget -- uv run --directory "/absolute/path/to/budget-mcp" server.py
```
To persist data, set `DATABASE_URL` in a `.env` file first (see [Database configuration](#๏ธ-database-configuration)).
---
## ๐ Available tools
<details>
<summary><strong>12 tools โ click to expand full reference</strong></summary>
| Tool | Description | Arguments |
| :--- | :--- | :--- |
| `budget_dashboard` | Interactive UI app: category breakdown chart and searchable transaction table. | `search` (str, opt)<br>`month` (str `YYYY-MM`, opt)<br>`type` (`income`\|`expense`, opt)<br>`limit` (int, default 100) |
| `spending_trends` | Interactive UI app: spending over time with category line chart, granularity toggle, date-range slider and searchable table. | `granularity` (`month`\|`week`\|`day`, default `month`)<br>`days_range` (range list `[start, end]`, opt)<br>`category_id` (int, opt)<br>`type` (`expense`\|`income`, opt)<br>`start_date` (str, opt)<br>`end_date` (str, opt)<br>`limit` (int, default 1000) |
| `add_transaction` | Logs one or many income/expense transactions. | `items` (list of dicts, opt โ batch)<br>`amount` (float, opt)<br>`category_id` (int, opt)<br>`description` (str, opt)<br>`type` (`expense`\|`income`, opt)<br>`date` (str `YYYY-MM-DD`, opt) |
| `get_summary` | Aggregated summary: income, expense, net balance, optional category breakdown. | `month` (str `YYYY-MM`, opt)<br>`start_date` / `end_date` (str `YYYY-MM-DD`, opt)<br>`category_id` (int, opt)<br>`type` (`income`\|`expense`, opt)<br>`by_category` (bool, default False) |
| `get_transactions` | Detailed transaction records by filter. | `category_id` (int, opt)<br>`type` (`income`\|`expense`, opt)<br>`month` (str, opt)<br>`start_date` / `end_date` (str, opt)<br>`min_amount` / `max_amount` (float, opt)<br>`search` (str, opt)<br>`limit` (int, default 50) |
| `get_uncategorized_transactions` | Uncategorised transactions sorted by description, for bulk categorisation. | `type` (`income`\|`expense`, opt)<br>`search` (str, opt)<br>`limit` (int, default 100) |
| `update_transaction` | Updates one or many transactions. | `items` (list of update dicts, opt)<br>`transaction_id` (int, opt)<br>`amount` (float, opt)<br>`category_id` (int, opt)<br>`description` (str, opt)<br>`type` (str, opt)<br>`date` (str `YYYY-MM-DD`, opt) |
| `delete_transaction` | Removes one or many transactions by ID. | `transaction_ids` (int or list of int) |
| `list_categories` | Lists active categories. | `type` (`expense`\|`income`, opt) |
| `add_category` | Adds one or many categories. | `items` (list of dicts, opt)<br>`name` (str, opt)<br>`type` (`expense`\|`income`, opt)<br>`description` (str, opt) |
| `update_category` | Updates a category's properties. | `category_id_or_name` (str)<br>`new_name` (str, opt)<br>`type` (str, opt)<br>`description` (str, opt) |
| `delete_category` | Removes one or many categories, optionally reassigning their transactions. | `category_ids_or_names` (str, int or list)<br>`reassign_to_category_id` (int, opt) |
</details>
---
## โ๏ธ Database configuration
Set via the `DATABASE_URL` environment variable in a `.env` file. Keep `.env` out of version control.
**Local SQLite** โ in-memory (default if `DATABASE_URL` is unset):
```env
DATABASE_URL=sqlite:///:memory:
```
**Local SQLite file** โ persists between restarts:
```env
DATABASE_URL=sqlite:///data/budget.db
```
**PostgreSQL / Neon:**
```env
DATABASE_URL=postgresql://<user>:<password>@<hostname>/<dbname>?sslmode=require
```
Tables and 15 default category seeds are created automatically on first startup.
---
## ๐ Client setup
<details>
<summary><strong>Claude Code, Claude Desktop, Cursor, Open WebUI</strong></summary>
### Claude Code (CLI)
```bash
claude mcp add budget -- uv run --directory "/absolute/path/to/budget-mcp" server.py
```
### Claude Desktop
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"personal-budget": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/budget-mcp", "server.py"]
}
}
}
```
### Cursor IDE
**Settings โ Features โ MCP โ Add New MCP Server**
- **Type:** `command`
- **Name:** `budget-mcp`
- **Command:** `uv run --directory "/absolute/path/to/budget-mcp" server.py`
### Open WebUI
Bridge the stdio server over HTTP with `mcpo`:
```bash
uvx mcpo --port 8000 -- uv run server.py
```
Then in **Admin Panel โ Settings โ External Tools**, add the OpenAPI connection URL `http://localhost:8000` (or `http://host.docker.internal:8000` from Docker).
</details>
---
## โ๏ธ Cloud deployment
For remote hosts, Docker, or platforms such as Horizon:
1. Set `DATABASE_URL` to a cloud PostgreSQL connection string in the deployment environment โ in-memory SQLite will not persist across restarts.
2. Point the runner at the ASGI app:
```bash
fastmcp run server.py:mcp
```
Schema tables and default categories initialise on import, so no migration step is needed on first boot.
---
## ๐งช Testing
```bash
uv run pytest
```
Inspect tools interactively with the FastMCP Inspector:
```bash
uv run fastmcp dev inspector server.py:mcp
```
Or preview interactive UI applications directly in the browser:
```bash
uv run fastmcp dev apps server.py:mcp
```
---
## ๐ License
MIT โ see [LICENSE](LICENSE).TDQS
Scored across 12 tools
Most tools are clearly distinct in purpose - categories, transactions, summaries, trends, and dashboard each have a unique role. There's minor potential confusion between get_summary and spending_trends (both aggregate data), and list_categories vs add_category are distinct enough. Overall boundaries are clear.
The naming mostly follows a consistent verb_noun pattern: list_categories, add_transaction, get_summary, update_transaction, delete_category. Minor deviations exist: 'budget_dashboard' and 'spending_trends' use noun-phrase naming instead of the verb-first convention, breaking the pattern slightly.
12 tools is well-scoped for a budget management server covering full lifecycle operations on both transactions and categories, plus reporting/visualization tools. Each tool earns its place without redundancy or bloat.
The surface provides full CRUD for transactions (add, get, update, delete) and categories (add, update, delete, list), plus specialized workflows like bulk import/categorization (get_uncategorized_transactions), and reporting (get_summary, spending_trends, budget_dashboard). No significant gaps in the budget domain.