Expense MCP Server
by Mannu-Thakur
README.md
# πΈ Expense MCP Server
A production-ready **Model Context Protocol (MCP) server** for managing personal expenses β built with FastMCP, SQLAlchemy, and Pydantic. Deploys as a Docker container on Render with PostgreSQL, and connects to Claude Desktop, Cursor, VS Code, or any MCP-compatible client.
---
## Status
β
**Live on Render** β Docker Β· PostgreSQL Β· Streamable HTTP
| | |
|---|---|
| **Transport** | Streamable HTTP |
| **Database** | PostgreSQL (Render managed) |
| **Health endpoint** | `/health` |
| **MCP endpoint** | `/mcp` |
---
## Architecture
```
Claude Desktop / Cursor / VS Code MCP
β
β MCP over Streamable HTTP
βΌ
βββββββββββββββββββββββ
β Expense MCP Server β Docker Β· Render
β FastMCP + Uvicorn β
ββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Expense Service β Business logic
ββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Repository β Data access layer
ββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β SQLAlchemy ORM β
ββββββββββ¬βββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β PostgreSQL / SQLiteβ Prod / Dev
βββββββββββββββββββββββ
```
### API Request Flow
```
Claude β MCP Tool β FastMCP β Expense Service β Repository β SQLAlchemy β PostgreSQL
```
---
## β¨ Features
- **8 MCP Tools** β add, list, update, delete, search expenses, and get monthly/category/merchant summaries
- **Dual transport** β STDIO (local Claude Desktop) and Streamable HTTP (remote/Docker/Render)
- **Dual database** β SQLite for local development, PostgreSQL for production
- **Production-ready** β structured logging, custom exceptions, connection pooling, non-root Docker user
- **31 tests** β full repository, service, and tool coverage with in-memory SQLite isolation
- **One-command deploy** β `render.yaml` Blueprint auto-provisions PostgreSQL + Docker web service
### Production Features
| Feature | Detail |
|---|---|
| β Docker multi-stage image | Minimal, hardened runtime layer |
| β PostgreSQL | Render-managed with connection pooling |
| β Render Blueprint | Auto-provisions all infrastructure |
| β Health endpoint | `/health` for Render and load balancer checks |
| β Remote HTTP MCP | Accessible at `/mcp` from any MCP client |
| β Connection pooling | `pool_size=5`, `max_overflow=10`, `pool_recycle=1800` |
| β Structured logging | ISO timestamps, log levels, logger names |
| β Non-root container | `appuser` with no escalation |
| β Automatic DB provisioning | Tables created on first startup |
---
## Compatible Clients
β
Claude Desktop
β
Cursor
β
VS Code MCP extension
β
Any MCP client supporting Streamable HTTP
---
## Transport Modes
| Transport | When to use |
|---|---|
| `stdio` | Local Claude Desktop β server runs as a child process |
| `streamable-http` | Remote deployment, Docker, Render β server listens on HTTP |
---
## π Project Structure
```
expense-mcp-server/
βββ app/
β βββ config.py # Pydantic Settings (reads from .env)
β βββ exceptions.py # ExpenseNotFoundError, ExpenseValidationError
β βββ logging_config.py # Structured logging setup
β βββ mcp_instance.py # FastMCP instance + /health endpoint
β βββ server.py # Registers all 8 tools
β βββ database/
β β βββ db.py # Engine, SessionLocal, validate_connection()
β β βββ models.py # Expense SQLAlchemy model
β βββ schemas/
β β βββ expense.py # ExpenseCreate, ExpenseUpdate (Pydantic)
β βββ repositories/
β β βββ expense_repository.py
β βββ services/
β β βββ expense_service.py
β βββ tools/
β β βββ add_expense.py
β β βββ list_expenses.py
β β βββ update_expense.py
β β βββ delete_expense.py
β β βββ search_expenses.py
β β βββ monthly_summary.py
β β βββ category_summary.py
β β βββ top_merchants.py
β βββ utils/
β βββ dates.py
β βββ currency.py
βββ tests/
β βββ conftest.py # In-memory SQLite fixtures + monkeypatch
β βββ test_expense.py # 31 tests
βββ run.py # Entry point
βββ requirements.txt
βββ Dockerfile # Multi-stage Docker build
βββ docker-compose.yml # App + PostgreSQL local stack
βββ render.yaml # Render Blueprint (PostgreSQL + Docker web service)
βββ .env # Local environment variables
```
---
## π MCP Tools
| Tool | Description |
|---|---|
| `add_expense` | Add a new expense with amount, category, date, merchant, currency |
| `list_expenses` | List all expenses sorted by date descending |
| `update_expense` | Update any field of an existing expense by ID |
| `delete_expense` | Delete an expense by ID |
| `search_expenses` | Search by category, description, or merchant |
| `monthly_summary` | Total, count, average, and max for the current month |
| `category_summary` | Spending totals grouped by category |
| `top_merchants` | Top merchants by total spending |
---
## βοΈ Environment Variables
| Variable | Default | Description |
|---|---|---|
| `APP_ENV` | `development` | `development` or `production` |
| `LOG_LEVEL` | `INFO` | Python log level |
| `MCP_SERVER_NAME` | `Expense Tracker` | Name shown in Claude Desktop |
| `TRANSPORT` | `stdio` | `stdio` or `streamable-http` |
| `HOST` | `0.0.0.0` | Bind host (HTTP mode only) |
| `PORT` | `8000` | Bind port for local/Docker. **Render injects `PORT=10000` automatically β no manual configuration needed.** |
| `DATABASE_URL` | `sqlite:///./expenses.db` | SQLite or PostgreSQL connection string |
---
## π Getting Started
### Option A β Local Development (STDIO + SQLite)
**1. Clone and create a virtual environment**
```bash
git clone https://github.com/your-username/expense-mcp-server.git
cd expense-mcp-server
python -m venv .venv
```
**2. Activate the virtual environment**
```bash
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
```
**3. Install dependencies**
```bash
pip install -r requirements.txt
```
**4. Configure `.env`**
```env
APP_ENV=development
LOG_LEVEL=INFO
MCP_SERVER_NAME=Expense Tracker
TRANSPORT=stdio
HOST=0.0.0.0
PORT=8000
DATABASE_URL=sqlite:///./expenses.db
```
**5. Run the server**
```bash
python run.py
```
The server starts in STDIO mode β ready to be used with Claude Desktop.
---
### Option B β Local Docker + PostgreSQL (HTTP)
**1. Start the full stack**
```bash
docker-compose up --build
```
This starts:
- `expense_postgres` β PostgreSQL 16 on port 5432
- `expense_mcp` β MCP server on port 8000 (Streamable HTTP transport)
The app waits for PostgreSQL to pass its health check before starting.
**2. Verify it's running**
```bash
curl http://localhost:8000/health
```
---
### Option C β Deploy to Render (Docker + PostgreSQL)
The project deploys as a **Docker multi-stage image** on Render.
**1. Push your repo to GitHub**
**2. Go to [render.com](https://render.com) β New β Blueprint**
**3. Connect your GitHub repo** β Render auto-detects `render.yaml` and automatically creates:
- A **managed PostgreSQL database**
- A **Docker web service** running `run.py`
- All required **environment variables**
- **Database connection** wired via `fromDatabase.connectionString`
**4. Your server will be live at:**
```
https://<your-render-service>.onrender.com
```
> Replace `<your-render-service>` with your actual Render service name (shown in the dashboard after deployment).
---
## π Health Check
The server exposes `/health` for Render and load balancer health checks.
```bash
curl https://<your-render-service>.onrender.com/health
```
**Response:**
```json
{"status": "ok"}
```
---
## π Verify the MCP Endpoint
```
GET https://<your-render-service>.onrender.com/mcp
```
> **Note:** A browser will show `405 Method Not Allowed`. This is expected β `/mcp` speaks the MCP protocol (not plain HTTP GET). Use an MCP client to connect.
---
## π₯ Claude Desktop Configuration
Add to `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS).
### Option A β Local STDIO
```json
{
"mcpServers": {
"expense-tracker": {
"command": "C:\\path\\to\\expense-mcp-server\\.venv\\Scripts\\python.exe",
"args": ["C:\\path\\to\\expense-mcp-server\\run.py"]
}
}
}
```
### Option B β Render (Remote HTTP)
```json
{
"mcpServers": {
"expense-tracker": {
"type": "streamable-http",
"url": "https://<your-render-service>.onrender.com/mcp"
}
}
}
```
### Option C β Docker (Local HTTP)
```json
{
"mcpServers": {
"expense-tracker": {
"type": "streamable-http",
"url": "http://localhost:8000/mcp"
}
}
}
```
> You can include all three entries simultaneously β just give each a unique key.
---
## π§ͺ Running Tests
```bash
python -m pytest tests/ -v
```
Expected output:
```
31 passed in ~1s
```
The test suite covers:
- `TestExpenseRepository` β 13 tests (CRUD + search + summaries)
- `TestExpenseService` β 10 tests (business logic + error handling)
- `TestTools` β 8 tests (MCP tool integration)
Tests use an **in-memory SQLite database** with full transaction rollback isolation between each test.
---
## π Database
### SQLite (Local)
Zero configuration. The `expenses.db` file is created automatically on first run.
```env
DATABASE_URL=sqlite:///./expenses.db
```
### PostgreSQL (Docker / Production)
```env
DATABASE_URL=postgresql://expense_user:expense_pass@localhost:5432/expense_db
```
The `Expense` table is created automatically via `Base.metadata.create_all()` on startup.
| Column | Type | Notes |
|---|---|---|
| `id` | Integer | Primary key |
| `amount` | Float | Required, must be > 0 |
| `category` | String | Required |
| `description` | String | Optional |
| `merchant` | String | Optional |
| `payment_method` | String | Optional |
| `currency` | String | Default: `INR` |
| `expense_date` | Date | Required |
| `created_at` | DateTime | Auto-set to UTC now |
---
## π¦ Tech Stack
| Layer | Library | Version |
|---|---|---|
| MCP Framework | `mcp` (FastMCP) | 1.28.1 |
| Settings | `pydantic-settings` | 2.14.2 |
| ORM | `SQLAlchemy` | 2.0.51 |
| Validation | `pydantic` | 2.13.4 |
| PostgreSQL driver | `psycopg2-binary` | 2.9.10 |
| HTTP server | `uvicorn` + `starlette` | β |
| Testing | `pytest` | 9.1.1 |
---
## π License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues