@maxs05/mcp-buxfer
v0.1.1
Published
MCP server for the Buxfer personal-finance API, powered by the @maxs05/node-buxfer SDK
Maintainers
Readme
mcp-buxfer
MCP server for Buxfer personal finance, built on the @maxs05/node-buxfer SDK. Seven task-shaped tools for reviewing spending, finding records and categorising transactions. Stateless, independently deployable, Streamable HTTP and stdio.
Tools
| Tool | Purpose |
|------|---------|
| buxfer_search_transactions | Search by merchant or description text, with totals and merchant-key candidates |
| buxfer_get_transactions_by_merchant_keys | Fetch transactions for confirmed merchant keys |
| buxfer_summarise_spending | Totals by tag, account, month or merchant over a window |
| buxfer_list_transactions | One page of transactions by date, account, tag, contact, group or status |
| buxfer_find_records | List or search accounts, tags, contacts, groups, reminders (id source for other tools) |
| buxfer_list_uncategorised_transactions | Untagged transactions with a next-page cursor |
| buxfer_set_transaction_tags | Set or add tags on up to 25 transactions; tags must already exist. Dry-run plan unless confirm: true |
Full table with SDK operations and omitted operations: docs/capability-matrix.md. Design: docs/architecture.md. No resources, prompts or generic capability layer. Tags, reminders, groups and contacts are read-only because Buxfer has no write endpoints for them.
Install and run
Requires Node.js 24+.
npx @maxs05/mcp-buxfer --transport stdio # stdio
npx @maxs05/mcp-buxfer --transport http # Streamable HTTP on 127.0.0.1:3000/mcp (default transport)From source: pnpm install && pnpm build && node dist/cli.js --transport http.
Container
docker build -t mcp-buxfer .
docker run --rm -p 3000:3000 -e MCP_API_KEY=<long-random-value> mcp-buxferThe image runs as non-root, contains no secrets, and sets HOST=0.0.0.0 so the port is reachable. That makes the endpoint public to whatever can reach it: set MCP_API_KEY or put an authenticating, rate-limiting gateway in front. The container healthcheck probes /health; use /health and /ready for orchestrator probes.
Transports and client configuration
Credentials are your Buxfer email and password, supplied by the client per session. The server stores none.
stdio
The client launches the process and passes credentials in its environment:
{
"mcpServers": {
"buxfer": {
"command": "npx",
"args": ["-y", "@maxs05/mcp-buxfer", "--transport", "stdio"],
"env": {
"BUXFER_EMAIL": "<your-buxfer-email>",
"BUXFER_PASSWORD": "<your-buxfer-password>"
}
}
}
}Streamable HTTP
Endpoint /mcp. Send credentials as headers on every request (a gateway may inject them):
{
"mcpServers": {
"buxfer": {
"type": "http",
"url": "https://<your-host>/mcp",
"headers": {
"X-Buxfer-Email": "<your-buxfer-email>",
"X-Buxfer-Password": "<your-buxfer-password>",
"Authorization": "Bearer <MCP_API_KEY, only if the server sets one>"
}
}
}
}Operational endpoints: /health and /ready (open, no Buxfer call) and /metadata (requires the bearer when MCP_API_KEY is set; returns no runtime details). Credentials are never accepted via CLI flags, tool arguments, query strings or files.
Configuration
Non-secret environment variables. Values above a hard ceiling are rejected at startup, not clamped.
| Variable | Default | Purpose |
|----------|---------|---------|
| HOST | 127.0.0.1 (image: 0.0.0.0) | HTTP bind address |
| PORT | 3000 | HTTP port |
| LOG_LEVEL | info | silent, error, warn, info or debug (stderr JSON) |
| MCP_API_KEY | unset | When set (at least 32 characters), HTTP requires Authorization: Bearer <key> |
| ALLOWED_HOSTS | loopback only on a loopback bind; unset (no Host check) on any other bind | Comma-separated Host allowlist (DNS-rebinding protection); set it behind a named host or proxy. /health and /ready are exempt so probes keep working |
| ALLOWED_ORIGINS | loopback only | Comma-separated Origin allowlist (host names or full origins); no CORS headers are sent |
| MAX_BODY_BYTES | 1048576 | HTTP body limit (max 4 MiB) |
| MCP_READ_ONLY | false | true hides buxfer_set_transaction_tags (6 tools listed) |
| BUXFER_TIME_ZONE | UTC | IANA zone for "today" and default windows |
| BUXFER_TIMEOUT_MS | 15000 | Per Buxfer request attempt (1000 to 30000) |
| BUXFER_RETRIES | 2 | SDK retries, 0 to 3 (writes only on HTTP 429) |
| BUXFER_MAX_SCAN_RECORDS | 2000 | Cap on records read by summaries and scans (100 to 5000) |
| BUXFER_UNCATEGORISED_MAX_PAGES | 5 | Vendor pages read per uncategorised call (1 to 10) |
| BUXFER_EMAIL, BUXFER_PASSWORD | unset | stdio only; set in the client config, never in .env.example |
Security model
- Stateless and multi-tenant: a new SDK client, MCP server and transport per call or request. Nothing is cached across requests; one Buxfer login per tool call.
- Credentials: only the two headers (HTTP) or two env vars (stdio). Missing credentials return a fixed authentication error without calling Buxfer. Logs redact
authorization,x-buxfer-*, tokens and passwords; URLs are never logged. - Outputs are field-allowlisted, and all free text from Buxfer (bank descriptions, merchants, names) is wrapped as
<untrusted_content>: treat it as data, never instructions. This is an advisory marker, not a guarantee against prompt injection. - The only write is tags on existing transactions; unknown tag names are always refused (use exact names from
buxfer_find_records), and a confirmed edit whose stored tags differ from the request is reported as failed and stops the batch. It is a dry-run plan by default and writes only withconfirm: true. The model sets that flag, so it is a speed bump, not human approval; rely on your client's tool approval, and SetMCP_READ_ONLY=truewhere writes are not wanted. - HTTP validates Host (on a loopback bind, or whenever
ALLOWED_HOSTSis set) and Origin, sends no CORS headers by default, limits body size, and uses only POST on/mcp. - TLS: the server does not terminate TLS. Anywhere non-loopback, run behind TLS, because the password travels in a header.
- Open relay risk (accepted).
MCP_API_KEYis optional. With it unset, anyone who can reach the port can submit arbitrary Buxfer credentials and use this server as a credential-checking relay from your egress IP. The default bind is127.0.0.1; binding publicly withoutMCP_API_KEYlogs a startup warning. For public exposure, setMCP_API_KEYor front the server with a gateway that authenticates and rate-limits. - Vendor residual. Buxfer carries its session token in the URL query string, so it can appear in Buxfer logs and in any TLS-intercepting proxy on your egress path. Each call creates a token that is not revoked.
Known limitations and unverified Buxfer behaviour
- Amount sign: live-checked for all four types (see Live verification).
buxfer_summarise_spendingstill counts expenses only by default and reports raw vendor values by type and currency. - Buxfer auto-creates unknown tag names when they are sent directly (live-verified), which is why the write tool always refuses unknown names.
- Group list filters, Reminder and Group live shapes, and the statement-upload response are unverified (docs-derived); the test account has no groups or reminders.
- API coverage "12 of 12 operations" is verified against an OpenAPI spec hand-built by the SDK author from vendor docs; Buxfer publishes no official spec.
- The write tool requires SDK >= 0.1.1 (0.1.0 reported every successful edit as failed). The SDK is pinned to exactly 0.1.2 (dev-toolchain-only release; runtime behaviour unchanged from 0.1.1).
- Login rate limit. A burst of about 13 logins triggered failures. The server is stateless (one login per tool call), so space calls or expect
authenticationerrors under bursts. - One login per call: heavy use can hit Buxfer's login rate limit (
rate_limited). - Discovery scans are bounded (
BUXFER_MAX_SCAN_RECORDS); results carrycomplete/truncatedflags when capped. buxfer_list_uncategorised_transactionspaginates on vendor page boundaries, so one call can return somewhat more thanlimitrows.buxfer_set_transaction_tagsis capped at 25 transactions per call and stops the batch on an uncertain failure; re-list before repeating.
Live verification
2026-10-11, SDK 0.1.1, real Buxfer account, throwaway rows only. buxfer_set_transaction_tags with confirm: true updated expense, income, refund and transfer rows: the tag was applied and amount, type, description, account and date were unchanged. Amounts read back positive for all four types. Buxfer auto-creates unknown tag names when they are sent directly, so the tool always refuses unknown names. Groups and reminders could not be live-checked (the account has none).
Pre-release checklist
- Run an image CVE scan (the Docker base images are already pinned by digest).
- Owner removes the leftover test tag
mcp-buxfer-verify-tagin Buxfer. - Decide repository visibility.
Gateway assumptions
Optional. A gateway may authenticate clients, rate-limit, terminate TLS and inject X-Buxfer-* headers. The server works without one but then is the edge (see the open relay risk).
Development and testing
See CONTRIBUTING.md. Tests use a fixture-backed fake SDK (mock mode in src/mocks); no test calls the real Buxfer API.
Troubleshooting
| Symptom | Cause and fix |
|---------|---------------|
| Buxfer authentication failed: check X-Buxfer-Email / X-Buxfer-Password | Wrong or missing credentials. HTTP: check headers. stdio: check env in the client config. Buxfer reports login failure as HTTP 400; the server maps it to this authentication error. |
| 401 from /mcp | MCP_API_KEY is set; send Authorization: Bearer <key>. |
| 403 from /mcp | Host or Origin not allowed. Set ALLOWED_HOSTS / ALLOWED_ORIGINS for your host name. |
| 405 on GET/DELETE /mcp | Expected: the server is stateless, POST only. |
| Container unreachable from host | Bound to loopback. Use HOST=0.0.0.0 (image default) and publish the port; then set MCP_API_KEY. |
| rate_limited | Buxfer limits logins or requests. Wait for retryAfterSeconds and reduce call frequency. |
| upstream_unavailable | Network, timeout or Buxfer 5xx. For writes, re-list before retrying. |
| Totals look partial | truncated: true: narrow the date range or filters. |
| buxfer_set_transaction_tags not listed | MCP_READ_ONLY=true hides it. |
| Intermittent authentication errors in a burst | Buxfer login rate limit (about 13 logins in a burst triggered failures); space calls out. |
License
MIT. Author: Max Soukhomlinov.
