Family Expense MCP
Family Expense AI — MCP-Based Expense Assistant
A real, working family expense/income/budget assistant whose financial capabilities are exposed as MCP (Model Context Protocol) tools through a FastMCP server, backed by PostgreSQL. This is not a chatbot with a database bolted on — MCP is the core of the architecture: any MCP-compatible LLM client (Claude Desktop, or Claude via a remote connector) discovers and invokes these tools directly.
Architecture
User
|
AI / LLM (e.g. Claude)
|
MCP Protocol
|
FastMCP Server (main.py)
|
MCP Tools (23 tools)
+----------+----------+-----------+
| | | |
Family Expenses Income Reports/Budgets/Insights
| | | |
+----------+----------+-----------+
|
PostgreSQLRemote usage (Claude connecting to a deployed server):
Claude -> MCP Connector -> HTTPS -> Remote MCP Server (Render)
-> FastMCP -> MCP Tools -> PostgreSQLThe LLM only ever understands the request, selects a tool, and
generates parameters. Validation, business logic, calculations, and all
persistence happen in app/services/*, with PostgreSQL as the single
source of truth.
Multi-user family usage (one shared database, many Claude accounts)
Each family member can run their own Claude account/client connected to
the same deployed MCP server (https://<your-app>.onrender.com/mcp).
When someone says "I spent ₹500 on lunch," their Claude calls add_expense
with member_name set to that person — the server resolves the name to a
member_id and writes it into the one shared Postgres database. So the
whole family's spending naturally converges into a single source of truth,
per person, without needing separate databases per user.
Tips for this setup:
Register everyone once with
add_family_member(e.g. Praneeth → Self, Rahul → Brother, Mom → Mother).Give each person's Claude a system/custom instruction like "My name is Rahul — when using Family Expense tools, use member_name='Rahul' unless I say otherwise." so they don't have to repeat their name every time.
Every report/insight/budget tool accepts an optional
member_nameto scope results to one person, or omit it for the whole family.
Project structure
mcp-famil/
├── main.py # FastMCP server: 23 MCP tools, thin wrappers
├── chatbot.py # Local NL test harness (no MCP client needed)
├── tool_router.py # Dispatches tool name + params -> service call
├── formatter.py # Raw results -> clean, INR-formatted text
├── requirements.txt
├── .env.example
├── .gitignore
├── render.yaml # One-file Render deployment config
│
├── app/
│ ├── db/
│ │ ├── connection.py # psycopg2 pool + init_db()
│ │ └── schema.sql # Table definitions
│ ├── services/ # Business logic + validation (DB access only here)
│ │ ├── errors.py
│ │ ├── validators.py
│ │ ├── date_utils.py
│ │ ├── family_service.py
│ │ ├── expense_service.py
│ │ ├── income_service.py
│ │ ├── summary_service.py
│ │ ├── report_service.py
│ │ ├── budget_service.py
│ │ └── insight_service.py
│ └── ai/
│ ├── schemas.py # Anthropic tool-use schemas (mirrors main.py)
│ ├── intent_parser.py # NL -> (tool, params) via Claude tool-use
│ ├── category_detector.py # Keyword-based expense category guesser
│ ├── reports.py # Rule-based report phrase parsing
│ ├── budget.py # Rule-based budget phrase parsing
│ └── insights.py # Suggestion generation from real DB numbers
│
└── tests/Database schema
family_members(id, name, relationship, created_at)
expenses(id, member_id → family_members, amount, category, expense_date, description, created_at, updated_at)
income(id, member_id → family_members, amount, source, income_date, notes, created_at, updated_at)
budgets(id, category, amount, created_at, updated_at)
Applied automatically on startup via init_db() (idempotent
CREATE TABLE IF NOT EXISTS), so there's no separate migration step to run.
MCP tools (23)
Area | Tools |
Family |
|
Expenses |
|
Income |
|
Summary |
|
Reports |
|
Budgets |
|
Insights |
|
Every tool is a thin wrapper: it calls tool_router.route() (which calls
into app/services/*) and formats the result with formatter.py before
returning it — so the response an LLM/user sees is already a clean,
₹-formatted message, not a raw JSON blob.
Currency
Everything is INR (₹), formatted with Indian digit grouping
(e.g. ₹12,34,567.00, not ₹1,234,567.00). Amounts are stored as
NUMERIC(12,2) in Postgres and only formatted at the presentation layer.
Setup
Install dependencies
pip install -r requirements.txtConfigure environment
cp .env.example .env # edit .env: set DATABASE_URL to your PostgreSQL connection string # (LLM_API_KEY is only needed for chatbot.py, not for the MCP server itself)Run the MCP server
python main.py # Tables are created automatically. MCP endpoint: http://localhost:8000/mcp(Optional) Try the local natural-language chatbot
python chatbot.pyExample session:
You: Rahul spent ₹700 on petrol today. AI: ✅ Expense Added 👤 Rahul 💸 ₹700.00 📂 Petrol 📅 2026-09-06
Local testing with an MCP client
With python main.py running, point any MCP-compatible client at
http://localhost:8000/mcp (Streamable HTTP transport) and confirm all 23
tools are discoverable. For example, using the FastMCP CLI:
fastmcp dev main.pyNote: depending on your installed
fastmcpversion, the transport name passed tomcp.run()inmain.pymay need to be"streamable-http"instead of"http". Checkfastmcp --version/ release notes if the server fails to start.
Using Supabase as the database
Supabase's database is plain PostgreSQL, so no code changes are
needed — just point DATABASE_URL at it:
Create a project at supabase.com.
Go to Project Settings → Database → Connection string → URI. Use the Session pooler (or Transaction pooler) connection string, not the direct connection — it's the one meant for long-running apps and works fine with this project's connection pool.
Copy it into
DATABASE_URLin your.env(or your host's environment variables), replacing[YOUR-PASSWORD]with your actual DB password:DATABASE_URL=postgresql://postgres.xxxxxxxxxxxx:[YOUR-PASSWORD]@aws-0-xx-xxxx-x.pooler.supabase.com:5432/postgresRun
python main.py(or redeploy your host) —init_db()creates thefamily_members,expenses,income, andbudgetstables on Supabase automatically on first boot.SUPABASE_URL/SUPABASE_KEYare not needed for this — those are only for the Supabase client SDK, which this project doesn't use.
Deployment
You need somewhere to run the always-on Python process — Supabase only hosts the database, not the app. Any host that gives you a public HTTPS URL works (Claude's remote connector requires HTTPS, not plain HTTP). Whatever you choose, the server just needs:
DATABASE_URLset to your Supabase connection stringTo listen on
0.0.0.0:$PORT(already handled bymain.py)The
/mcppath reachable over HTTPS at the root of your public URL
render.yaml is included for a one-file Render deploy if you want it:
Build command:
pip install -r requirements.txtStart command:
python main.pyEnvironment variables:
DATABASE_URL,LLM_API_KEY(optional),ANTHROPIC_MODEL(optional)
If you're self-hosting on your own server/VPS instead, make sure a
reverse proxy (Caddy, nginx + certbot, etc.) terminates TLS in front of
main.py and forwards to it — Claude cannot connect to a bare http://
URL or a self-signed certificate.
Verify the deployed server before adding it to Claude
curl -s -D - -o /dev/null -X POST https://<your-domain>/mcp \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'You should get HTTP/1.1 200 back with a serverInfo block naming
"Family Expense MCP". If this fails, fix it before adding the connector —
Claude will hit the same URL.
Add it to Claude as a custom connector
On claude.ai, go to Settings → Connectors.
Click Add custom connector.
Enter a name (e.g. "Family Expense AI") and the URL:
https://<your-domain>/mcpSave, then enable the connector in a chat (the tool/connector picker below the message box).
Test it: ask Claude something like "Add a family member named Praneeth, relationship Self" or "I spent ₹500 on lunch today" — Claude should call the corresponding MCP tool and show the formatted result.
Every family member can add the same URL as their own custom connector from their own claude.ai account — they all talk to the same hosted server and the same Supabase database, so everyone's expenses land in one shared, per-person-tracked source of truth (see "Multi-user family usage" above).
Example natural-language flow
User: "Rahul spent ₹700 on petrol today."
LLM determines intent:
add_expense,member_name=Rahul,amount=700,category=Petrol,expense_date=todayadd_expensetool →tool_router.route()→expense_service.add_expense()Resolves "Rahul" →
member_id, validates the amount, inserts the rowResult formatted and returned:
✅ Expense Added 👤 Rahul 💸 ₹700.00 📂 Petrol
Error handling
Every tool call is wrapped so it never crashes the server. Domain errors
(ValidationError, NotFoundError) are caught and returned as a friendly
⚠️ ... message — covering: unknown family member, invalid/negative
amount, missing required parameter, unparseable date, expense/income not
found, no budget set for a category, unknown tool, and any unexpected
exception (❌ Unexpected error: ...).
Security
No credentials are hardcoded anywhere. DATABASE_URL and LLM_API_KEY
are read from the environment (.env, gitignored). .env.example
documents every variable without real values.
Running tests
pytestUnit tests (formatter, validators, date parsing, category detection,
rule-based NL parsing, tool routing) run with no external services.
tests/test_integration.py exercises real database round-trips and is
skipped automatically unless DATABASE_URL points at a database.
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/Praneeth2383/mcp-famil'
If you have feedback or need assistance with the MCP directory API, please join our Discord server