Expense Tracker MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Expense Tracker MCP ServerLog โน450 I spent on groceries today."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
๐ฐ Expense Tracker MCP Server
Talk to your expenses instead of typing them into a spreadsheet.
A local-first personal finance tracker built as a Model Context Protocol (MCP) server. It runs entirely on your own machine โ no hosting, no cloud, no public endpoint โ and connects to Claude Desktop over stdio, the transport MCP uses for a client launching a server as a local subprocess. Logging, editing, and analyzing expenses happens through plain conversation instead of a UI.
"I spent โน450 on groceries today." โ Claude Desktop launches this server as a local subprocess โ calls a tool on it โ a validated row lands in a SQLite file on your disk โ "Got it โ logged โน450 under food/groceries."
No form. No dropdown. No app to open. No server to deploy or endpoint to secure, either โ everything runs on your machine, under your own account, exactly like a CLI tool would. Just describe what happened, and an LLM turns it into a structured, validated database write โ and can just as easily turn it back into an answer to "how much did I spend on food this month?"
Why this project exists
MCP (Model Context Protocol) is the open standard, introduced by Anthropic in late 2024, that lets AI applications like Claude call out to real tools and real data instead of just generating text. It's quickly become one of the standard ways serious AI applications are wired together โ and this project is a from-scratch, hands-on implementation of one: a real server, exposing real tools, backed by a real database, connected to a real client.
It's small on purpose. The point isn't a big feature surface โ it's demonstrating, concretely, the full loop: designing an MCP tool schema an LLM can call reliably, validating untrusted input from a model the same way you'd validate input from a user, and structuring the codebase so it doesn't fall over the moment it needs a second feature.
Related MCP server: Expense Tracker MCP Server
Architecture
flowchart LR
U(["๐ค You"]) -- "natural language" --> H["Claude Desktop\n(MCP Host + Client)"]
H <-- "JSON-RPC 2.0 over stdio" --> S["Expense Tracker\nMCP Server (FastMCP)"]
S --> DB[("SQLite\nexpenses.db")]
S --> CAT["categories.json"]
style H fill:#6E56CF,color:#fff
style S fill:#2088FF,color:#fff
style DB fill:#333,color:#fffClaude Desktop is the host โ the application you actually talk to. It embeds an MCP client, which speaks a standard JSON-RPC 2.0 protocol to this server over stdio (Claude Desktop launches it as a local subprocess). The server exposes two kinds of capability: tools the model can invoke (add_expense, summarize, ...) and a resource it can read (expense://categories).
What actually happens on one message
sequenceDiagram
participant U as You
participant C as Claude (Host)
participant S as MCP Server
participant D as SQLite
U->>C: "I spent โน450 on groceries today"
C->>C: decides add_expense is the right tool
C->>S: call_tool("add_expense", {date, amount, category, ...})
S->>S: validate date, amount > 0, category exists
alt input invalid
S-->>C: ToolError with a clear message
C-->>U: explains what was wrong
else input valid
S->>D: INSERT INTO expenses(...)
D-->>S: new row id
S-->>C: {"status": "ok", "id": 19}
C-->>U: "Got it โ logged โน450 for groceries."
endThe model never touches SQL and never sees your database file โ it only ever sees the tool's declared inputs and outputs. Every validation rule lives on the server, not in the prompt, which is the entire point of doing this as an MCP server instead of just asking an LLM to "remember" your expenses in a chat.
How the server itself is layered
flowchart TD
server["server.py\nFastMCP instance ยท tool & resource definitions\nthe ONLY module that knows MCP exists"]
db["db.py\nsqlite3 access โ zero MCP imports"]
cat["categories.py\ncategory/subcategory rules"]
val["validation.py\ndate & range rules"]
cfg["config.py\npaths, env-var overridable"]
server --> db
server --> cat
server --> val
db --> cfg
cat --> cfg
val --> cfg
style server fill:#2088FF,color:#fff
style db fill:#333,color:#fffdb.py never imports fastmcp and never catches its own errors โ a sqlite3.Error just propagates. Translating a failure into an MCP-friendly ToolError happens only in server.py. That's a deliberate boundary, not an accident: the data-access layer stays reusable and testable completely independently of the protocol sitting on top of it.
Features
Tools โ functions the model can call:
Tool | What it does |
| Insert an expense (date, amount, category, subcategory, note) |
| Fetch one expense by id |
| List expenses in a date range |
| Partially update an expense โ only the fields you pass change |
| Delete an expense by id |
| Sum expenses by category over a date range |
| Write matching expenses to a CSV file |
| Return the category/subcategory reference data |
Resources โ data the client can read directly:
Resource | What it serves |
| The full category/subcategory list, read live from |
Engineering highlights
A few decisions worth calling out, because they're the difference between "it works" and "I'd trust this":
Input validation treats the model like an untrusted caller. Every tool parameter is
Annotated[type, Field(...)]โ Pydantic constraints (Field(gt=0)on amounts) are enforced by FastMCP before a function body runs, and dates/categories are checked against real calendar rules andcategories.jsonbefore anything touches the database.The data layer is protocol-agnostic by design.
db.pydoesn't know MCP exists. That single decision is what makes the test suite below possible without any mocking gymnastics.Configuration is environment-driven, not hardcoded.
EXPENSE_TRACKER_DB_PATHand friends let the exact same code run against a real database or a disposable test one, with zero code changes.The test suite proves the server, not just the SQL. Unit tests hit
db.pydirectly; the integration suite drives the server through FastMCP's real in-memory client โ the same call path Claude Desktop uses โ so schema validation and error translation are actually exercised, not assumed.Backward compatibility was a deliberate constraint, not an afterthought: when the project moved from a single script to a proper package (see
ROADMAP.md, Phase 3), the rootmain.pywas kept as a thin launcher specifically so an already-configured Claude Desktop setup wouldn't break.
Project structure
expense-tracker-mcp-server/
โโโ main.py # backward-compatible launcher
โโโ categories.json # category/subcategory reference data
โโโ expenses.db # SQLite database (gitignored)
โโโ exports/ # CSV exports (gitignored)
โโโ pyproject.toml # deps, console-script entrypoint, pytest config
โโโ src/expense_tracker_mcp_server/
โ โโโ __init__.py # real console-script entrypoint
โ โโโ config.py # paths, env-var overridable
โ โโโ db.py # sqlite3 access โ no MCP imports
โ โโโ categories.py # category/subcategory validation
โ โโโ validation.py # date validation
โ โโโ server.py # FastMCP instance, tools, resource
โโโ tests/
โ โโโ conftest.py # isolated throwaway DB per test
โ โโโ test_db.py
โ โโโ test_categories.py
โ โโโ test_validation.py
โ โโโ test_server_integration.py # via FastMCP's real client
โโโ .github/workflows/test.yml # CI
โโโ ROADMAP.md # phase-by-phase build logGetting started
Requires uv and Python 3.14+.
git clone https://github.com/<your-username>/expense-tracker-mcp-server.git
cd expense-tracker-mcp-server
uv sync
uv run expense-tracker-mcp-server # starts the MCP server over stdioConnect it to Claude Desktop
Add this to Claude Desktop's claude_desktop_config.json (Settings โ Developer โ Edit Config):
{
"mcpServers": {
"expense-tracker": {
"command": "uv",
"args": [
"--directory", "/absolute/path/to/expense-tracker-mcp-server",
"run", "expense-tracker-mcp-server"
]
}
}
}Restart Claude Desktop, then just talk to it:
"Log โน250 for groceries today." "What did I spend on food in August?" "Export my August expenses to CSV."
Configuration
Variable | Default | Overrides |
|
| the SQLite file |
|
| the category data |
|
| where CSV exports land |
Testing
uv run pytest -v30 tests: unit tests against the data and validation layers directly, plus an integration suite that calls tools through FastMCP's real in-memory client rather than the raw Python functions โ proving the server behavior, not just the SQL underneath. Every test runs against its own disposable SQLite file; nothing ever touches real data. Runs automatically on every push via GitHub Actions.
Roadmap
This project is being hardened in deliberate, documented phases โ see ROADMAP.md for the full write-up of what's done, what's next, and the reasoning behind each decision.
Phase | Status |
1 โ Input validation & error handling | โ Done |
2 โ Full CRUD + CSV export | โ Done |
3 โ Layered package structure | โ Done |
4 โ Test suite + CI | โ Done |
5 โ Logging & observability | โณ Next |
6 โ Packaging polish (Docker, linting) | Planned |
7 โ Stretch: budgets, MCP prompts, HTTP transport | Backlog |
What this project demonstrates
Designing and implementing an MCP server from scratch (tools, a resource, schema design) with FastMCP
Treating LLM-supplied input as untrusted, with real validation and clean error surfaces
Layered architecture with a deliberate protocol boundary, not just "more files"
A real, verified test suite (unit + protocol-level integration) with CI
Environment-based configuration instead of hardcoded paths
Writing documentation that's actually useful to someone other than the author
License
MIT โ see LICENSE.
Author
Ansh Gautam โ anshgautam1011@gmail.com
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Log, query, and edit expenses, budgets, and accounts in Manilo from any MCP-compatible AI assistant.
Log, query, and edit expenses, budgets, and accounts in Manilo (formerly Ledgy) from any MCP-compatible AI assistant.
Log, query, and edit expenses, budgets, and accounts in Ledgy from any MCP-compatible AI assistant.
Personal finance by conversation: expenses, receipts, statement import, budgets, net worth.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables users to track personal expenses through natural language interactions with comprehensive category support and financial summaries. Provides both local and remote MCP server options with SQLite storage for fast expense management operations.
- FlicenseNot gradedqualityDmaintenanceEnables natural language management of personal expenses, including adding, listing, and summarizing expenses with local SQLite storage.
- FlicenseNot gradedqualityBmaintenanceA lightweight MCP server that lets LLM clients track, query, and summarize personal expenses using a local SQLite database.
- FlicenseBqualityCmaintenanceA lightweight local MCP server that enables users to add, list, edit, and delete expenses via SQLite database through natural language in MCP-compatible clients.41
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/anshgautam-github/mcp-expense-tracker'
If you have feedback or need assistance with the MCP directory API, please join our Discord server