moneybags
Integrates as an OIDC authentication provider and can also serve as a reverse proxy for secure access to the server.
Integrates as an OIDC authentication provider, allowing users to sign in with their existing Authentik accounts.
Integrates as an OIDC authentication provider, allowing users to sign in with their Google accounts.
Integrates as an OIDC authentication provider, enabling sign-in through Keycloak.
Integrates as an OIDC authentication provider, allowing users to authenticate via Okta.
Used as a local AI model provider for categorizing transactions and generating insights.
Used as an AI model provider for categorizing transactions and generating written insights.
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., "@moneybagsHow much did I spend on groceries in July?"
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.
Moneybags
A self-hosted personal finance ledger that you can talk to.
Import statements or sync your banks. Transactions get categorized by rules first and a model second, and every correction you make teaches a rule so the same merchant is never miscategorized twice. Then ask about your money in plain language — Moneybags runs an MCP server, so Claude reads your ledger directly.
"How much did I spend on groceries in August?"
"I just bought coffee, about six dollars"
"That Venmo payment was for tree work, not uncategorized"One deployment, one owner. Your transactions live in your database, your API keys are yours, and there is no service in the middle.
What this is, and what it isn't
It is a ledger for someone who wants their financial data in a database they control, with a categorizer that can be corrected and a conversational interface that isn't a chatbot bolted onto a dashboard.
It isn't a budgeting app with a mobile client and a support team. There is no signup, no multi-tenancy, no hosted version. If you want an app your family can log into on their phones, use Monarch or YNAB — genuinely, they are good at that and this is not trying to be.
Running it costs whatever your database and API keys cost. For a personal ledger on Neon's free tier with statement upload only, that is nothing.
Related MCP server: OpenCoffer
Why the design is the way it is
Nearly every hard decision in this codebase is about not silently losing money. Not crashing — losing. A ledger that drops a category is annoying; a ledger that drops $6,000 and still balances is dangerous, because it looks correct.
These are the rules that follow from that:
Money is integer cents. bigint in the schema, number in TypeScript.
Floats appear only at the formatting boundary. Nothing sums a float, ever.
Negative means money left. Applied in parsing, storage, ledger math and UI,
so a period's net cashflow is a plain SUM(amount_cents) with no per-row
branching. An import adapter that gets this backwards produces a ledger that is
internally consistent and entirely wrong, which is why it is the one thing
adapters are told twice.
is_transfer is not a category. It means this exact dollar is already
counted somewhere else in this ledger — an internal transfer that names the
other account, or a credit card payment whose purchases are also imported. It
is emphatically not for Venmo, Zelle, Cash App, ATM withdrawals, or savings
contributions. Money that left is spending, whichever rail carried it.
A payment rail is not a merchant. "Venmo" tells you how money moved and nothing about what it bought. Those rows are charged as spending immediately — so an unanswered question never quietly reduces the month's total — and queued for you to label. One answer teaches a rule keyed to the counterparty.
Manual classifications are never overwritten. Every automated pass filters
on classification_source <> 'manual'. Your answer outranks any rule and any
model.
Dedupe is by fingerprint, not by statement. sha256(account, date, amount, normalized description) with a unique index. Upload overlapping statements in
any order; rows already present are skipped. The account is part of that
fingerprint, which is why an unfiled import is refused once you have more than
one account.
Income can only be lost one way. Totals split by sign, so a positive amount
counts as income whatever category it lands in — an imperfect category still
counts, and a classification failure costs nothing. is_transfer is the single
point of failure, so a rule may only set it on an inflow when the pattern names
a payment outright or names the other account. pnpm db:audit-income lists
every inflow and every rule currently able to exclude one.
The classifier refuses to guess. Rules run first. Whatever is left goes to a model. Anything still unresolved lands in a review queue, not a confident wrong answer — and descriptions that structurally cannot carry a purpose skip the model entirely, because it would answer "unknown" every time at a cost.
Setup
Node 20+, pnpm, and a Postgres database.
git clone https://github.com/YOUR-USERNAME/moneybags && cd moneybags
pnpm install
cp .env.example .env.localFill in three things:
# 1. Your database
DATABASE_URL="postgresql://..."
# 2. A session secret
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
# 3. A password
pnpm auth:hash 'the password you want' # prints APP_PASSWORD_HASH=...Then:
pnpm db:migrate
pnpm db:seed # idempotent; seeds the category taxonomy
pnpm devThat is a working ledger with CSV statement import. Everything below is optional and the app is honest about what each one adds.
Optional: a model
Set AI_API_KEY. You get model-assisted categorization for merchants no rule
recognizes, written-up insights, and PDF/image statement reading.
Without it, rules still categorize, unmatched rows go to the review queue, and CSV import is unaffected. That is a supported way to run this, not a broken one.
Any provider works — Anthropic, OpenAI, OpenRouter, Groq, or a local Ollama or
LM Studio. See docs/ai.md. PDF reading needs Anthropic; every
other feature works anywhere.
Optional: bank syncing
Connect accounts through Plaid instead of uploading statements. Plaid's free tier covers 10 connections and includes transactions.
You do not need this. Statement upload is a complete way to use the app,
and skipping Plaid means one less third party holding credentials to your
bank. See docs/plaid.md, which also explains the free-tier
trap worth knowing before you start.
Optional: talking to it
Settings → issue an MCP token, then point any MCP client at
https://your-host/api/mcp with that bearer token. Fourteen tools for reading,
logging and correcting. There is deliberately no delete tool — a misheard
instruction must not be able to destroy a record.
Deploying
Two documented paths, neither privileged over the other:
Docker Compose — app plus Postgres, nothing else needed:
cp .env.example .env # set APP_PASSWORD_HASH and SESSION_SECRET
docker compose up -dVercel + Neon — free tier, no server to run.
Both are in docs/deploy.md, including the reverse-proxy
setup if you want it behind Authelia or Tailscale.
Authentication
Three methods; configure at least one or the app refuses to start rather than serving your finances to anyone who finds the URL.
Method | For |
Password | The default. Store a scrypt hash via |
OIDC | Any standards-compliant provider — Google, Authentik, Keycloak, Zitadel, Okta. An allowlist is required; an empty one denies everyone. |
Trusted header | Already behind Authelia, oauth2-proxy, Cloudflare Access or Tailscale. Only safe when the app is unreachable except through the proxy. |
Login is rate-limited, passwords are compared in constant time, sessions are
signed JWTs revocable in bulk via SESSION_VERSION, and Plaid access tokens
are AES-256-GCM encrypted at rest. See
docs/security.md for the threat model and what it does
not protect against.
Composability
Transactions enter through one boundary: src/lib/sources. Everything
downstream — dedupe, reconciliation, classification, the ledger — sees only
ParsedTransaction[] and cannot tell whether a row arrived by upload or sync.
Adding a bank, an aggregator, or an awkward CSV dialect is an adapter and nothing else:
registerFileSource({
id: "my-bank",
label: "My Bank CSV",
accepts: ({ filename }) => filename.startsWith("mybank-"),
parse: ({ bytes }) => ({ transactions: parseMyBank(bytes), warnings: [] }),
});The model provider is behind the same kind of seam — no vendor SDK is imported
outside src/lib/ai. docs/extending.md covers the
taxonomy, rules, sources, sync providers and MCP tools.
Commands
| the usual |
| generate |
| schema, then taxonomy |
| re-run the pipeline over the ledger, skipping manual rows |
| verify no inflow is being excluded from income |
| what is linked, and each account's sync boundary |
Stack
Next.js 15 (App Router), Postgres via Drizzle, Tailwind. The Anthropic SDK and Plaid SDK are both optional at runtime and isolated behind interfaces.
Contributing
CLAUDE.md documents why things are the way they are, usually
naming the bug that caused them. Read it before touching
src/lib/classify or src/lib/reconcile — several regressions are pinned by
tests, and the comments say what breaks if you undo them.
The general rule when changing the classifier: prefer under-matching to over-matching. A rule that never fires again costs one re-correction. A rule that over-matches silently rewrites history you already checked.
License
MIT — see LICENSE.
This handles real financial data. It comes with no warranty, and your deployment, keys and backups are your responsibility.
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 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.
- AlicenseNot gradedqualityCmaintenanceEnables querying personal finance data including accounts, transactions, spending, holdings, net worth, and budgets from your self-hosted OpenCoffer instance. Supports natural language queries through any MCP-compatible client.14MIT
- AlicenseAqualityDmaintenancePersonal expense tracker MCP server that enables tracking expenses, income, budgets, and savings goals through natural language.10MIT
- FlicenseNot gradedqualityCmaintenanceExposes personal-finance tools like accounts, transactions, spending analysis, budgets, bills, reminders, portfolio, and goals via MCP, enabling any MCP client to query financial data.
Related MCP Connectors
Personal finance by conversation: expenses, receipts, statement import, budgets, net worth.
Log, query, and edit expenses, budgets, and accounts in Ledgy from any MCP-compatible AI assistant.
Ask your AI about bank accounts, spending, debts, holdings, and investment activity.
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/aroesec/moneybags'
If you have feedback or need assistance with the MCP directory API, please join our Discord server