Forward a receipt to your inbox and it's filed automatically.
git clone https://github.com/assaf/expense{
"mcpServers": {
"expense": {
"command": "node",
"args": ["/path/to/expense/dist/index.js"],
"env": {
"DATABASE_URL": "<database_url>",
"RESEND_API_KEY": "<resend_api_key>",
"INBOUND_EMAIL_WEBHOOK_SECRET": "<inbound_email_webhook_secret>",
"DEEPSEEK_API_KEY": "<deepseek_api_key>"
}
}
}
}DATABASE_URLRESEND_API_KEYINBOUND_EMAIL_WEBHOOK_SECRETDEEPSEEK_API_KEYMCP Servers overview
# Expense
> Expense is a receipt tracker with no friction: receipts arrive by email and file themselves, mileage prices itself from IRS rates, and an AI assistant does the data entry you'd rather skip — capture, log, and ask, all through MCP.
## Screenshots


What it does:
- Log expenses two ways: receipt-based (upload/scan an image, add date, report,
category, merchant, amount) and mileage (drive route on a map — Leaflet + OSRM
— with configurable per-year mileage rates)
- **Receipts by email**: forward a receipt to your inbox address and it's
parsed (merchant/amount/category) and added automatically — see below
- Organize into reports and categories; every receipt image is stored and
auto-renamed to a convention (YYYY-MM-DD_Report_Name.jpg)
- Export: PDF per report (with embedded receipt images) and a ZIP of everything
- Settings: reports, categories, mileage rates, home location for mileage routes
Stack:
- React Router v8 (framework mode) + Tailwind v4, TypeScript
- Postgres via Prisma — accounts, users, expenses, reports, categories,
settings, mileage, image blobs
- Images: Postgres BYTEA (prod and dev/test) — no external store
- Deployed on Vercel + Neon (git push to main auto-deploys)
Auth & accounts (the recent work):
- Email/password login (scrypt-hashed), sessions via signed cookies
- Multi-user accounts: each account has its own data, users in the same account
share everything, other accounts fully isolated
- New users join an account via an 8-char invite code (shown in Settings,
regenerable); anyone can self-signup into a fresh account
- Image keys are namespaced per account so two accounts can never collide
## Accounts & sharing
Login is email/password (scrypt-hashed) — the email is the login name, stored
lowercase and unique, format-validated at signup/join. Every expense, report,
category, and setting belongs to an **account**; everyone in an account shares
them, and accounts are fully isolated from each other.
- **Sign up** → creates a brand-new account (starts empty).
- **Join** → enter an account's invite code (Settings → Account) to share
that account's data.
- The first account/user is bootstrapped from `APP_EMAIL`/`APP_PASSWORD`
when the database is empty; pre-email accounts get their login backfilled
from `APP_EMAIL` on first start (initStore).
## SEO & AI discovery
The public marketing pages double as the site's AI-search surface: when
someone asks an assistant for a free expense tracker, GPTBot / OAI-SearchBot /
ClaudeBot / PerplexityBot crawl and quote them. The copy is written as
standalone, quotable answers that name the app and its URL, and it lives in
ONE place — `app/lib/seo-content.ts` — which renders every surface:
| Page | Purpose |
| ---------------------------------------- | -------------------------------------------------------------- |
| `/` | Landing page (SoftwareApplication JSON-LD) |
| `/about` | Full feature/benefit list (AboutPage JSON-LD) |
| `/faq` | 13 Q&As matching real AI queries (FAQPage JSON-LD) |
| `/alternatives` | Expense vs Expensify comparison (WebPage + FAQPage JSON-LD) |
| `/llms.txt` | The llmstxt.org file — the curated overview AI assistants read |
| `/about.md` `/faq.md` `/alternatives.md` | Markdown mirrors per the llms.txt convention |
Supporting plumbing: `public/robots.txt` explicitly allows the AI crawlers
(GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot / Claude-SearchBot /
Claude-User, PerplexityBot / Perplexity-User, Google-Extended,
Applebot-Extended, meta-externalagent) while app routes stay blocked, and
`public/sitemap.xml` lists the public pages.
These routes are public (see the root loader in `app/root.tsx`); everything
else still requires a session.
## What it does
- Track **receipt** expenses (date, merchant, amount, image, category, report)
and **mileage** expenses (date, 2+ addresses, distance, amount, report).
- Mileage routes run **Home → stops → Home**; distance is computed via OSRM and
the amount from a per-year mileage rate. Maps use Leaflet + OpenStreetMap —
**no API keys required**.
- Incomplete expenses are highlighted so they're easy to finish.
- Paste (⌘V) or upload an image anywhere to start a new receipt.
- **Export**: each report as a PDF (grouped by category, with all receipt
images), or everything as a ZIP (CSV + images named `YYYY-MM-DD_REPORT_FILE.ext`).
- **AI assistants (MCP)**: any MCP client — Claude Code, Claude Desktop,
Cursor — connects by signing in with your account (OAuth; no API keys).
See [AI assistants](#ai-assistants-mcp) below.
## AI assistants (MCP)
The app speaks the Model Context Protocol at `https://expense.labnotes.org/mcp`
(auth: OAuth 2.1 authorization-code + PKCE — sign in with your account and
approve the connection; no API keys). An assistant connected to your account
can:
- **Capture a receipt** — a photo or PDF in the chat runs the same OCR +
extraction pipeline as the web app and is categorized from your own
merchant history.
- **Log a drive** — give it stops in plain English; it geocodes, routes, and
prices the trip at the year's IRS rate.
- **Answer spending questions** — "how much did I spend on flights last
quarter?" gets the exact total from your data.
- **Build reports** — create/close reports, move expenses into them, export
a report PDF.
- **Reconcile** — paste a bank statement CSV; it finds every charge with no
matching receipt (read-only).
Connect any MCP client:
```json
// Claude Code — .mcp.json (no headers needed: the client discovers OAuth)
{
"mcpServers": {
"expense": {
"type": "http",
"url": "https://expense.labnotes.org/mcp"
}
}
}
```
The client opens your browser; you sign in and click Allow. Manage
connections (per-token delete, full disconnect) in **Settings → Agents &
API (MCP)**. See [`docs/mcp.md`](docs/mcp.md) for the full reference and
[`docs/mcp-directories.md`](docs/mcp-directories.md) for the directory
listings.
## State
Storage is Postgres-only via **Prisma** (`prisma/schema.prisma` is the
single schema source of truth; the client is generated to
`prisma/generated` by `pnpm build:prisma`). `DATABASE_URL` is required at
startup (the app exits with a clear error otherwise). Receipt images live in
Postgres BYTEA (`image_blobs`) in prod and dev — no separate storage service.
| Data | Images |
| --------------------------- | ------------------------------ |
| `accounts` / `users` / | Postgres BYTEA (`image_blobs`, |
| `expenses` / `reports` / | prod and dev) |
| `categories` / `settings` / | |
| `mileage` / `image_blobs` | |
All reads/writes go through `app/lib/store.server.ts` (→
`app/lib/database.ts`, Prisma queries scoped by `accountId`); image storage
is behind `app/lib/images.server.ts` (Prisma `imageBlob`).
Keys are `images/{accountId}/...` pathnames on every backend — namespaced
per account so two accounts can never collide on the same filename.
Schema changes: edit `prisma/schema.prisma`, then `prisma migrate dev --name …` locally and
`pnpm db:push` (or `pnpm db:migrate`) before deploying.
### `data/` — removed
The file-era migration source (`data/*.csv` + `data/images/*` and the
`pnpm migrate-data` one-off) was deleted in the Jul 2026 cleanup — data now
lives in the database, and importing from Expensify happens via
`scripts/import-expensify.ts`. Cloning prod uses `scripts/clone`
(`prisma/backup.sql`).
## Quick start
## Environment variables
Load order: real `process.env` (Vercel dashboard, or inline) wins; a local
`.env` file fills the gaps. `DATABASE_URL` is required; `.env` is gitignored. if
(!hasDatabase()) {
**dev / test — local `.env`:**
```bash
# .env (project root, gitignored)
DATABASE_URL=postgres://assaf@localhost/expense_dev # include the local user
SESSION_SECRET=… # signs the session cookie (random hex)
APP_EMAIL=… # bootstrap: first account's email (empty DB only)
APP_PASSWORD=… # bootstrap: first account's password (empty DB only)
# Receipts by email (all optional):
# RESEND_API_KEY=re_… INBOUND_EMAIL_WEBHOOK_SECRET=whsec_…
# INBOUND_EMAIL_ADDRESS=receipts@example.com # forwarding + reply sender
# DEEPSEEK_API_KEY=sk-… RECEIPT_OCR_MODE=auto
```
On an empty database the first account + user are bootstrapped from
`APP_EMAIL`/`APP_PASSWORD` (fail-closed if missing); afterwards users are
created through the app's signup/join flow. `SESSION_SECRET` is always
required. `APP_EMAIL`/`APP_PASSWORD` can be removed from `.env` once you
have at least one user.
Accounts created before email login (username era) keep their old username
as the stored email until `APP_EMAIL` is set — `initStore` then backfills
that address onto the bootstrap (oldest) user, so the configured credentials
keep working.
Tests intentionally hardcode `expense_test` (Postgres incl. image blobs),
ignore the local database, and reset the schema from Prisma on each run
(`pnpm test:db:push` in the test setup).
**prod — Vercel:** set env vars in the project dashboard (Settings →
Environment Variables): `DATABASE_URL` (Vercel Postgres / Neon pooled URL),
`SESSION_SECRET`,
and (only until the first user exists) `APP_EMAIL` / `APP_PASSWORD`.
Vercel injects them at runtime; `.env` never exists there.
## Receipts by email
Forward a receipt email to your inbox address and it's parsed and added
automatically: the merchant, amount, and caWhat people ask about expense
What is assaf/expense?
+
assaf/expense is mcp servers for the Claude AI ecosystem. Forward a receipt to your inbox and it's filed automatically. It has 1 GitHub stars and was last updated today.
How do I install expense?
+
You can install expense by cloning the repository (https://github.com/assaf/expense) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is assaf/expense safe to use?
+
assaf/expense has not been audited yet by our security agent. Review the original repository on GitHub before using it in production.
Who maintains assaf/expense?
+
assaf/expense is maintained by assaf. The last recorded GitHub activity is from today, with 0 open issues.
Are there alternatives to expense?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy expense to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/assaf-expense)<a href="https://claudewave.com/repo/assaf-expense"><img src="https://claudewave.com/api/badge/assaf-expense" alt="Featured on ClaudeWave: assaf/expense" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
The fastest path to AI-powered full stack observability, even for lean teams.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!