Expense Tracker
# Expense Tracker MCP Server
An expense tracking application built with **FastMCP, PostgreSQL, and Claude Desktop**.
The project was initially developed and tested locally, with Claude Desktop acting as the MCP client and FastMCP handling tool execution and communication with PostgreSQL.
The project was later converted to use `async/await` and the FastMCP server was deployed remotely. The remote MCP server was successfully connected to Claude as a custom connector, but the PostgreSQL database is currently running locally, so the deployed server cannot access it.
The next step is to move PostgreSQL to a hosted environment and connect it to the deployed MCP server.
---
## Architecture
### Local Version
```text
Claude Desktop
|
| MCP over stdio
v
FastMCP Server
|
v
Async Python Tools
|
v
PostgreSQL
|
v
localhost:5432
```
### Remote Version
```text
Claude / MCP Client
|
| MCP over HTTP
v
FastMCP Cloud
|
v
Async Python Tools
|
v
PostgreSQL
```
The remote architecture is not fully operational yet because PostgreSQL is still running on the local development machine.
---
## Features
The MCP server currently provides six tools:
- `add_expense` — Add a new expense
- `list_expenses` — List expenses within a date range
- `summarize_expenses` — Get an expense summary and category-wise breakdown
- `edit_expense` — Update an existing expense
- `delete_expense` — Delete an expense by transaction ID
- `credit` — Add an income or credit transaction
---
## Tech Stack
- Python
- FastMCP
- PostgreSQL
- Psycopg 3
- `async/await`
- `python-dotenv`
- `uv`
- Claude Desktop
- FastMCP Cloud
---
## Project Structure
```text
expense-tracker-mcp-server/
│
├── db/
│ ├── __init__.py
│ └── connection.py
│
├── server.py
├── .env
├── .gitignore
├── pyproject.toml
└── uv.lock
```
---
## Database
The application uses PostgreSQL for persistent storage.
A single `transactions` table stores both expenses and credits. The `transaction_type` column determines whether a transaction represents an expense or a credit.
### Transactions Table
```sql
CREATE TABLE transactions (
id SERIAL PRIMARY KEY,
amount NUMERIC(12, 2) NOT NULL,
category VARCHAR(100) NOT NULL,
description TEXT,
transaction_type VARCHAR(20) NOT NULL
CHECK (transaction_type IN ('expense', 'credit')),
transaction_date DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```
---
## Requirements
Make sure the following are installed:
- Python 3.11+
- PostgreSQL
- `uv`
- Claude Desktop
---
## Installation
Clone the repository and move into the project directory:
```bash
git clone <repository-url>
cd expense-tracker-mcp-server
```
On Windows, if the repository is already cloned:
```powershell
cd C:\Github\expense-tracker-mcp-server
```
### Install Dependencies
```bash
uv sync
```
If the dependencies have not been added yet:
```bash
uv add fastmcp
uv add "psycopg[binary,pool]"
uv add python-dotenv
```
---
## Environment Variables
Create a `.env` file in the project root:
```env
DB_HOST=localhost
DB_PORT=5432
DB_NAME=expense-tracker
DB_USER=postgres
DB_PASSWORD=YOUR_POSTGRES_PASSWORD
```
Replace `YOUR_POSTGRES_PASSWORD` with your PostgreSQL password.
**Do not commit the `.env` file to Git.**
---
## Async PostgreSQL Connection
The project was initially implemented using synchronous PostgreSQL connections.
It was later converted to use asynchronous PostgreSQL connections with **Psycopg 3**.
The connection layer uses:
```python
psycopg.AsyncConnection
```
This allows database I/O to be awaited instead of blocking the execution flow while waiting for PostgreSQL.
The project is also prepared for connection pooling using Psycopg's async pooling support.
---
## Running the Local Server
From the project directory:
```bash
uv run fastmcp run server.py
```
The local server uses the `stdio` transport so Claude Desktop can communicate with it directly.
A successful startup should display a message similar to:
```text
Starting MCP server 'Expense Tracker' with transport 'stdio'
```
---
## Claude Desktop Configuration
Add the MCP server to the Claude Desktop configuration.
Example:
```json
{
"mcpServers": {
"Expense Tracker": {
"command": "C:\\Users\\Dell\\AppData\\Local\\Programs\\Python\\Python311\\Scripts\\uv.exe",
"args": [
"run",
"fastmcp",
"run",
"server.py"
],
"env": {},
"transport": "stdio",
"type": null,
"cwd": "C:\\Github\\expense-tracker-mcp-server"
}
}
}
```
The `cwd` should point to the location of the project on your machine.
After updating the configuration, restart Claude Desktop and verify that the **Expense Tracker MCP server** is connected.
---
## Remote Deployment
The FastMCP server was also deployed to **FastMCP Cloud**.
The remote MCP endpoint was successfully connected to Claude as a **custom connector**.
The remote MCP communication follows:
```text
Claude
|
| MCP over HTTP
v
FastMCP Cloud
|
v
Python Tools
|
v
PostgreSQL
```
However, the PostgreSQL database is currently running on the local development machine.
The deployed server cannot access:
```text
localhost:5432
```
because `localhost` from the remote server refers to the remote server itself, not the developer's local machine.
Therefore, the remote version currently requires a hosted PostgreSQL database.
---
## Usage
The tools can be used through natural language in Claude Desktop.
### Add Expense
Example:
```text
Add an expense of ₹500 for Entertainment with the description Movie.
```
The server stores the transaction in PostgreSQL and returns the transaction ID.
---
### List Expenses
Example:
```text
List my expenses from September 1, 2026 to September 5, 2026.
```
The tool returns expenses within the specified date range along with the total expense amount.
---
### Summarize Expenses
Example:
```text
Summarize my expenses from September 1, 2026 to September 5, 2026.
```
The summary includes:
- Total expenses
- Number of expenses
- Category-wise spending
---
### Edit Expense
Example:
```text
Change expense ID 2 to ₹650.
```
Individual fields can be updated without changing the remaining fields.
---
### Delete Expense
Example:
```text
Delete expense ID 2.
```
The server checks that the transaction exists and is an expense before deleting it.
---
### Add Credit
Example:
```text
Add a credit of ₹50,000 with category Salary and description September salary.
```
Credits are stored in the same `transactions` table with:
```text
transaction_type = 'credit'
```
---
## Database Connection
The PostgreSQL connection logic is separated into:
```text
db/connection.py
```
Database credentials are loaded from environment variables using `python-dotenv`.
Psycopg 3 is used for communication with PostgreSQL.
The project uses asynchronous PostgreSQL connections for the async version of the server.
---
## Transaction Handling
Database write operations use explicit transaction handling.
Successful operations are committed:
```text
SQL operation
|
v
commit()
```
If an error occurs:
```text
SQL operation
|
v
rollback()
```
This prevents failed database operations from being committed.
---
## Security
Database credentials are stored in `.env` rather than directly in the source code.
The `.gitignore` file includes:
```gitignore
.env
.venv/
__pycache__/
*.pyc
```
Never commit database credentials or other secrets to the repository.
---
## Development Progress
The project was developed incrementally:
### 1. MCP Concepts
Covered the fundamentals of:
- MCP architecture
- MCP clients and servers
- Tools
- Resources
- Prompts
- Tool calling
- MCP communication
### 2. Basic Local MCP Server
Created a basic FastMCP server and tested MCP tool execution locally.
### 3. Local Expense Tracker
Extended the server into an Expense Tracker using:
- FastMCP
- Python
- PostgreSQL
- Claude Desktop
The local version worked successfully with Claude Desktop as the MCP client.
### 4. Async Conversion
Converted the server from synchronous functions to `async/await` and moved towards asynchronous PostgreSQL operations.
### 5. Remote Deployment
Deployed the FastMCP server to FastMCP Cloud.
### 6. Custom Connector
Connected the deployed MCP server to Claude using a custom connector.
### 7. Current Deployment Limitation
The remote server cannot access the PostgreSQL instance running on the local machine.
The next step is to move PostgreSQL to a hosted environment and connect it to the deployed FastMCP server.
### 8. Next Phase
After completing the remote database setup, the project will move towards building an **MCP client** to understand and implement the client side of the MCP architecture.
---
## Current Status
### Completed
- MCP fundamentals
- Basic FastMCP server
- Local Expense Tracker
- PostgreSQL integration
- Six MCP tools
- Claude Desktop integration
- Async/await conversion
- FastMCP Cloud deployment
- Claude custom connector integration
### In Progress
- Move PostgreSQL to a hosted environment
- Connect remote FastMCP server to hosted PostgreSQL
- Production-ready connection pooling
- Improved project architecture
- MCP client implementation
---
## Future Improvements
Planned improvements include:
- Hosted PostgreSQL
- Repository layer
- Service layer
- Connection pooling
- Better input validation
- Improved error handling
- Balance calculation
- Detailed financial reports
- Automated tests
- Logging
- Database migrations
- Docker support
- CI/CD
- MCP client implementation
- Multi-client testing
---
## License
This project is built for learning and personal development.
TDQS
Scored across 6 tools
Each tool targets a distinct action: adding, listing, summarizing, editing, deleting expenses, or adding credits. The only potential overlap is between list_expenses and summarize_expenses, but one returns detailed entries while the other aggregates totals.
Most tools follow a consistent verb_noun pattern like add_expense, list_expenses, and delete_expense. The outlier is 'credit', which breaks the pattern and would be clearer as add_credit or add_income.
Six tools is a reasonable, focused size for an expense tracker. Each tool covers a necessary core operation without unnecessary bloat.
Expenses have full CRUD coverage, but credits/income can only be added—there is no way to list, summarize, edit, or delete credits. This creates a significant gap for a tracker that accepts income transactions, since users cannot manage or verify those records.