The problem
Most existing Actual Budget MCP servers were built as simple stdio bridges for single-user Claude Desktop sessions. They work fine on a developer laptop. They fall apart the moment you want a remote server for LibreChat or LobeChat, multi-user authentication, multiple budget files, or a real test suite covering more than the happy path.
Personal finance is an unforgiving domain. A misrouted transaction or a deleted category cascades through months of data. Operators need production tools, not toys.
What this does that the others do not
Two transports, one codebase
HTTP (--http) for remote clients like LibreChat. Stdio (--stdio) for local Claude Desktop. Same 63 tools either way. Most alternatives pick one.
Search transactions by month, amount, category, or payee. Aggregated summaries instead of dumping raw rows into the AI context window. Token-efficient and unique to this server.
Runtime multi-budget switching
Configure N budget files. The AI calls actual_budgets_switch mid-conversation. Personal becomes Family becomes Business without restarting anything.
Production reliability primitives
Connection pooling (up to 15 concurrent sessions), exponential-backoff retry, full test suite (unit + E2E + integration). The plumbing nobody talks about until it breaks.
OIDC multi-user with per-budget ACLs
JWKS-validated JWTs, AUTH_BUDGET_ACL for per-user budget scoping. Casdoor-tested. The only Actual MCP with real multi-tenancy.
Architecture
Each AI client speaks MCP over its preferred transport. The server normalises both into the same internal tool dispatcher, which wraps every Actual Budget API call in a retry-and-pool layer before touching the local SQLite copy.
┌─────────────┐ MCP/HTTP ┌──────────────────┐ Actual API ┌──────────────┐
│ LibreChat │ ◄────────────► │ Actual MCP │ ◄────────────► │ Actual │
│ LobeChat │ │ Server │ │ Budget │
│ (remote) │ │ (63 tools) │ │ Server │
└─────────────┘ └──────────────────┘ └──────────────┘
▲
┌─────────────┐ MCP/stdio │
│ Claude │ ◄──────────────────────►│
│ Desktop │ │
│ (local) │ │
└─────────────┘ │
▼
retry + pool + ACL + SQLite mirror
Every Actual API call goes through withActualApi() in src/lib/actual-adapter.ts, which handles init / shutdown lifecycle, retry (3 attempts, exponential backoff), and concurrency limiting.
Decisions worth knowing about
The MCP spec lets you ship one do_anything tool with a free-form prompt. I built 63 narrow tools instead, one per Actual operation, each with strict Zod-validated inputs. AI models route to the right tool more reliably; bad inputs fail at the schema, not after touching the database.
Why ship HTTP and stdio rather than just stdio
stdio is easier and most Actual MCPs stop there. But LibreChat and LobeChat are HTTP-native; forcing them through mcp-remote adds latency and an extra failure surface. Two transports doubles the test matrix but unlocks the production use case.
You cannot avoid the data volume
Actual Budget has no REST API. The official library downloads a local copy of your budget and syncs writes back. The server has to persist that copy or it re-downloads on every restart. Documented as a hard requirement in the Quick Start; misreading it is the number-one cause of failed first-time setups.