{"slug": "show-hn-xpenser-self-hostable-personal-finance-tracker-with-mcp-access", "title": "Show HN: Xpenser – self-hostable personal finance tracker with MCP access", "summary": "Xpenser, an open-source self-hostable personal finance tracker with API and MCP access, launched on Hacker News. The tool offers dashboards, categories, vendor tracking, multi-currency support, and optional AI-generated insights, serving as a reference app for the Cleverbrush Framework.", "body_md": "xpenser is an open-source, self-hostable personal finance tracker for people who want to track and analyze income and expenses with dashboards, categories, vendors, reports, and API/MCP access.\n\nIt grew out of a personal Telegram bot plus Google Sheets workflow. The project is early and still evolving, but it is useful enough to run, inspect, extend, self-host, or use as a working Cleverbrush Framework reference app. If you track or analyze expenses somewhere else today, feedback is welcome on whether xpenser could replace or complement that setup and what would need to be added.\n\nxpenser is also a real-world reference app for\n[Cleverbrush Framework](https://docs.cleverbrush.com), showing how a\nschema-first TypeScript stack can drive API contracts, validation, OpenAPI,\ntyped clients, React forms, auth-aware endpoints, observability, Telegram\nworkflows, and MCP access from one cohesive application.\n\n- Move everyday finance tracking into a structured app with dashboards, categories, vendors, reports, and searchable transaction history.\n- Track income, expenses, refunds, and returns with categories, notes, dates, vendors, and currencies.\n- Review daily, weekly, monthly, quarterly, and yearly summaries with category split and trend context.\n- Use multiple transaction currencies with automatic conversion to your default\ncurrency through\n[Frankfurter](https://www.frankfurter.app/). - Capture transaction scans, enrich vendor data, and keep setup workflows usable on mobile and desktop.\n- Receive optional weekly and monthly email summaries with OpenAI-generated spending and income insights.\n- Connect external tools through API keys, a typed Node client, an MCP server, and a Telegram bot.\n\n|\n|\n\nxpenser is intentionally small enough to inspect while still exercising production-shaped framework patterns:\n\n`packages/contracts`\n\ndefines the public contract with Cleverbrush schemas.`apps/api`\n\nexposes the contract through Cleverbrush server handlers, auth metadata, OpenAPI, DI, logging, tracing, and MCP.`packages/client`\n\nwraps the generated Cleverbrush client with retry, timeout, dedupe, batching, cache tags, and OpenTelemetry propagation.`packages/ui`\n\nbinds Cleverbrush schema fields to reusable React form controls.\n\nStart with [Cleverbrush Reference Notes](/cleverbrush/xpenser/blob/main/docs/cleverbrush-reference.md) if\nyou are here to learn the framework patterns behind the app. For upstream\nframework docs, use:\n\n- Node.js 22\n- npm 11\n- Docker with Docker Compose v2 (\n`docker compose`\n\n)\n\n```\nnpm install\ncp .env.example .env\n```\n\nThe defaults in `.env.example`\n\nare safe for local development and point the app\nat PostgreSQL on `localhost:5432`\n\n.\n\nThe apps import local packages from their built `dist`\n\noutputs, so build the\nshared packages once before starting dev servers:\n\n```\nnpm run build -w @xpenser/contracts\nnpm run build -w @xpenser/client\nnpm run build -w @xpenser/ui\ndocker compose up -d postgres\n```\n\nUseful checks:\n\n```\ndocker compose ps postgres\ndocker compose logs postgres\nnpm run dev\n```\n\nThe API runs database migrations on startup.\n\nLocal URLs:\n\n- Web app:\n[http://localhost:3000](http://localhost:3000) - API:\n[http://localhost:4000](http://localhost:4000) - API health check:\n[http://localhost:4000/health](http://localhost:4000/health) - OpenAPI JSON:\n[http://localhost:4000/openapi.json](http://localhost:4000/openapi.json)\n\nEmail/password sign-in works without any external auth provider. Accounts created this way must confirm their email before signing in.\n\nGoogle sign-in supports two modes:\n\n- Direct Google OAuth for self-hosted deployments.\n- Cleverbrush Passport for the hosted Cleverbrush deployment.\n\nSelect the mode with `GOOGLE_SIGN_IN_MODE`\n\n:\n\n```\nGOOGLE_SIGN_IN_MODE=auto\n```\n\n`auto`\n\nuses direct Google OAuth when `AUTH_GOOGLE_ID`\n\nand\n`AUTH_GOOGLE_SECRET`\n\nare configured. If those are not set, it uses Passport only\nwhen all Passport variables are configured. If neither auth provider is\nconfigured, the Google sign-in button is hidden and email/password sign-in still\nworks.\n\nUse `GOOGLE_SIGN_IN_MODE=direct`\n\nto require direct Google OAuth,\n`GOOGLE_SIGN_IN_MODE=passport`\n\nto require Passport, or\n`GOOGLE_SIGN_IN_MODE=disabled`\n\nto hide Google sign-in even when credentials are\npresent.\n\nCreate an OAuth 2.0 client in Google Cloud Console:\n\n- Application type: Web application\n- Authorized JavaScript origin: your public\n`APP_URL`\n\n- Authorized redirect URI:\n`${APP_URL}/api/auth/callback/google`\n\nFor local development with the default `APP_URL`\n\n, use:\n\n```\nhttp://localhost:3000/api/auth/callback/google\n```\n\nConfigure the web app with Auth.js-standard Google variables:\n\n```\nAPP_URL=https://xpenser.example.com\nNEXTAUTH_URL=https://xpenser.example.com\nNEXTAUTH_SECRET=replace-with-at-least-32-characters\nAUTH_SECRET=replace-with-the-same-value-as-NEXTAUTH_SECRET\nGOOGLE_SIGN_IN_MODE=auto\nAUTH_GOOGLE_ID=your-google-oauth-client-id\nAUTH_GOOGLE_SECRET=your-google-oauth-client-secret\n```\n\nGoogle accounts must have a verified email address. If a local email/password account already exists with the same email, Google sign-in is rejected instead of silently linking the accounts.\n\nPassport is a private Cleverbrush auth broker. Self-hosted deployments should use direct Google OAuth unless they run their own compatible Passport service.\n\nConfigure both services with:\n\n```\nGOOGLE_SIGN_IN_MODE=passport\nPASSPORT_BASE_URL=https://auth.cleverbrush.com\nPASSPORT_PROJECT=xpenser\nPASSPORT_ENVIRONMENT=production\nPASSPORT_PUBLIC_KEY=\n```\n\n`PASSPORT_PUBLIC_KEY`\n\nis optional. When empty, the API fetches\n`<PASSPORT_BASE_URL>/.well-known/public-key`\n\nand caches it in memory. If set,\nuse the base64-encoded PEM public key.\n\nFor a production-like local run, build and start the full Compose stack:\n\n```\ndocker compose up --build\n```\n\nThis starts the containerized web app, API, PostgreSQL, Swagger UI, and\nobservability services defined in `docker-compose.yml`\n\n.\n\nFull Docker URLs:\n\n- Web app:\n[http://localhost:3000](http://localhost:3000) - External API proxy:\n[http://localhost:3000/external-api](http://localhost:3000/external-api) - Swagger UI:\n[http://localhost:8090](http://localhost:8090) - SigNoz:\n[http://localhost:8080](http://localhost:8080)\n\nFor public deployments, put your reverse proxy in front of the web app and set\n`APP_URL`\n\nto the public origin. The API service stays private on the Docker\nnetwork and the Next app exposes it under `/external-api`\n\n.\n\nFor a smaller public deployment, use `docker-compose.prod.yml`\n\nas the starting\npoint and provide production secrets for `NEXTAUTH_SECRET`\n\n, `AUTH_SECRET`\n\n,\n`JWT_SECRET`\n\n, `WEB_API_SERVICE_SECRET`\n\n, and `TELEGRAM_BOT_SERVICE_SECRET`\n\n.\n\nThe default `.env.example`\n\nkeeps external integrations off unless you configure\ntheir provider credentials:\n\n- OpenAI email insights: set\n`OPENAI_API_KEY`\n\n,`OPENAI_REPORT_MODEL`\n\n,`RESEND_API_KEY`\n\n,`EMAIL_FROM`\n\n,`EMAIL_REPORTS_ENABLED=1`\n\n, and`EMAIL_REPORTS_SCHEDULER_ENABLED=1`\n\n. - Telegram bot workflows: set\n`TELEGRAM_BOT_TOKEN`\n\n,`TELEGRAM_BOT_USERNAME`\n\n, and`TELEGRAM_BOT_SERVICE_SECRET`\n\n. - Vendor enrichment: set\n`BRANDFETCH_API_KEY`\n\nor`BRANDFETCH_CLIENT_ID`\n\n, then enable`VENDOR_ENRICHMENT_ENABLED=1`\n\n. - Google sign-in: configure direct Google OAuth as described above, or leave it disabled and use email/password accounts.\n\nCreate an API key from Settings -> Preferences -> API keys. The API key can be used as a bearer token with curl or with the typed Node client:\n\n```\ncurl -X POST \"$APP_URL/external-api/transactions\" \\\n  -H \"Authorization: Bearer $XPENSER_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"categoryId\":1,\"amount\":12.34,\"currency\":\"USD\",\"effect\":\"normal\",\"occurredAt\":\"2026-05-13T12:00:00.000Z\"}'\njs\nimport { createXpenserClient } from '@xpenser/client';\n\nconst client = createXpenserClient({\n    baseUrl:\n        process.env.XPENSER_API_BASE_URL ??\n        'http://localhost:3000/external-api',\n    getToken: () => process.env.XPENSER_API_KEY ?? null\n});\n\nawait client.transactions.create({\n    body: {\n        categoryId: 1,\n        amount: 12.34,\n        currency: 'USD',\n        effect: 'normal',\n        occurredAt: new Date()\n    }\n});\n```\n\nOmit `effect`\n\nor set it to `normal`\n\nfor regular transactions. Use\n`effect: 'reversal'`\n\nfor refunds in expense categories or payments and\nchargebacks in income categories; the entered amount stays positive and reports\nsubtract it from that category.\n\n`X-API-Key: $XPENSER_API_KEY`\n\nis also accepted.\n\nxpenser exposes an MCP Streamable HTTP endpoint for AI agents at\n`/external-api/mcp`\n\n. Use the same API key from Settings -> Preferences -> API\nkeys as a bearer token. MCP tools can read and manage the API-key owner's\nvendors, categories, and transactions, so treat MCP access as full account data\naccess. Use a dedicated API key for MCP clients and revoke it when access is no\nlonger needed:\n\n```\n{\n  \"mcpServers\": {\n    \"xpenser\": {\n      \"type\": \"streamable-http\",\n      \"url\": \"https://xpenser.example.com/external-api/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${XPENSER_API_KEY}\"\n      }\n    }\n  }\n}\n```\n\nThe MCP server exposes tools for the current user, vendors, categories, transactions, dashboard summaries, and statistics. Vendor candidate search and enrichment may call the configured vendor enrichment provider.\n\n```\nnpm run lint\nnpm run typecheck\nnpm test\nnpm run test:e2e\n```\n\nDatabase helpers:\n\n```\nnpm run db:run -w @xpenser/api\ndocker compose stop postgres\ndocker compose down\ndocker compose down -v\n```\n\nThe e2e suite requires `PLAYWRIGHT_BASE_URL`\n\nwhen run outside the GitHub PR\nenvironment.\n\nContributions are welcome. Good first areas include documentation, self-hosting guides, framework reference notes, UI polish, API examples, and small focused product improvements.\n\n- Read\n[CONTRIBUTING.md](/cleverbrush/xpenser/blob/main/CONTRIBUTING.md)before opening a PR. - Use issues for actionable bugs and feature requests.\n- Use GitHub Discussions for questions, ideas, and Cleverbrush learning threads.\n- Keep Cleverbrush contract, endpoint metadata, and handler trees aligned when changing the API.\n\nxpenser is early, practical, and evolving. It has no meaningful user traction yet; feedback on product fit, README clarity, self-hosting, and MCP workflows is welcome. The goal is to remain useful as a personal finance app while staying clear enough for developers to learn how a Cleverbrush full-stack project fits together.\n\nxpenser does not currently ship bank sync, budget planning, net-worth tracking, native mobile apps, or mature import pipelines.\n\nxpenser is released under the [MIT License](/cleverbrush/xpenser/blob/main/LICENSE).", "url": "https://wpnews.pro/news/show-hn-xpenser-self-hostable-personal-finance-tracker-with-mcp-access", "canonical_source": "https://github.com/cleverbrush/xpenser", "published_at": "2026-06-17 08:34:59+00:00", "updated_at": "2026-06-17 08:52:27.034795+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools"], "entities": ["Xpenser", "Cleverbrush Framework", "Frankfurter", "OpenAI", "Telegram", "Google", "PostgreSQL", "Docker"], "alternates": {"html": "https://wpnews.pro/news/show-hn-xpenser-self-hostable-personal-finance-tracker-with-mcp-access", "markdown": "https://wpnews.pro/news/show-hn-xpenser-self-hostable-personal-finance-tracker-with-mcp-access.md", "text": "https://wpnews.pro/news/show-hn-xpenser-self-hostable-personal-finance-tracker-with-mcp-access.txt", "jsonld": "https://wpnews.pro/news/show-hn-xpenser-self-hostable-personal-finance-tracker-with-mcp-access.jsonld"}}