Skip to main content
Glama
zeeshanali45

Expense Tracker MCP Server

by zeeshanali45
README.md
# Building My Own MCP Server (Expense Tracker)

Most of my MCP work so far has been connecting *to* servers from LangGraph agents. This project is the other side: an MCP server built from scratch with FastMCP, so any MCP-compatible client can add, list, and summarize expenses through natural conversation.

## What it exposes

**Tools:**
- `add_expense(date, amount, category, subcategory="", note="")` — inserts a new expense row and returns the new row's id
- `list_expenses(start_date, end_date)` — returns all expenses in an inclusive date range, most recent first
- `summarize(start_date, end_date, category=None)` — aggregates total spend and entry count per category over a date range, optionally filtered to one category

**Resource:**
- `expense:///categories` — exposes the available expense categories as JSON, so a client can validate a category before calling `add_expense`. Falls back to a sensible default category list if `categories.json` is missing.

## How it's built

- **FastMCP** (`mcp.tool()` / `mcp.resource()` decorators) turns plain async functions into MCP-discoverable tools — no manual schema wiring needed, the docstring and type hints are enough for a client to know how to call them.
- **aiosqlite** for all runtime queries, so the server doesn't block while reading/writing the database — consistent with the async patterns I've used across the LangGraph chatbot and MCP client projects.
- **Synchronous `sqlite3` used once, at startup only** — `init_db()` creates the table (with `PRAGMA journal_mode=WAL` for safer concurrent access) and does a throwaway insert/delete to confirm the process actually has write access to the database file before the server starts accepting requests.
- **Database stored in the OS temp directory** (`tempfile.gettempdir()`) rather than a fixed path, so the server works regardless of where it's deployed or who's running it, without permission issues.
- Runs over **streamable HTTP** (`mcp.run(transport="http", ...)`), so it can be reached remotely — this is the same server referenced as the `"expense"` MCP connection in my LangGraph + MCP client project and the RAG-enabled chatbot platform.

There's also a small `proxy.py`, which exposes the same remote server over **stdio** instead — useful for MCP clients (like Claude Desktop) that expect a local stdio-based server rather than a remote HTTP URL.

## What building the server side (not just the client side) clarified

Connecting *to* MCP servers made them feel like a black box with a protocol I had to satisfy. Building one showed that an MCP tool is genuinely just an async Python function with a docstring and typed parameters — FastMCP's job is making that function *discoverable and callable* by any compliant client, not adding complexity to the function itself. The interesting engineering is still normal backend concerns: database access patterns, concurrency (WAL mode), and graceful fallbacks (the categories resource, the write-access check at startup).

## Tools

Python, FastMCP, aiosqlite, SQLite (WAL mode)

## What I'd build next

- Connect this server directly into my persistent LangGraph chatbot platform, so "how much did I spend on food last month?" is answerable in an ongoing conversation instead of a one-off script
- Add an `update_expense` / `delete_expense` tool — right now entries can only be added, not corrected
- Add input validation on `category` against the categories resource, instead of accepting any string