Skip to main content
Glama
Iyongbudiarso

moneylover-mcp

README.md
# moneylover-mcp

[![CI](https://github.com/Iyongbudiarso/moneylover-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Iyongbudiarso/moneylover-mcp/actions/workflows/ci.yml)
[![Security audit](https://github.com/Iyongbudiarso/moneylover-mcp/actions/workflows/audit.yml/badge.svg)](https://github.com/Iyongbudiarso/moneylover-mcp/actions/workflows/audit.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/Iyongbudiarso/moneylover-mcp/badge)](https://scorecard.dev/viewer/?uri=github.com/Iyongbudiarso/moneylover-mcp)
[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-com.iyongbudiarso%2Fmoneylover--mcp-blue)](https://registry.modelcontextprotocol.io/v0.1/servers?search=com.iyongbudiarso/moneylover-mcp)
[![Glama](https://glama.ai/mcp/servers/Iyongbudiarso/moneylover-mcp/badges/score.svg)](https://glama.ai/mcp/servers/Iyongbudiarso/moneylover-mcp)

An [MCP](https://modelcontextprotocol.io) server that lets an AI agent or chatbot read your
[Money Lover](https://moneylover.me) data and, if you allow it, add, correct and delete transactions. It talks to
the client over **stdio**.

> **Disclaimer — please read.**
> This project is **unofficial**. It is **not affiliated with, endorsed by, or supported by Money Lover**
> or its owners. "Money Lover" is a trademark of its respective owner and is used here only to
> describe what the software connects to.
> Money Lover has no public API. This tool uses the same undocumented web endpoints as the Money Lover
> web app, reverse-engineered from public open-source projects. They can change or break at any time,
> and using them may be against Money Lover's terms of service (which we could not verify).
> **You use this software entirely at your own risk**, including the risk of account restrictions,
> lost or incorrect data, and incorrect transactions. It is provided "AS IS" under the MIT license,
> without warranty of any kind. Back up your data and try it on a throwaway wallet first.

## Trust model

What a reviewer should know before granting this server access to a finance account. Each claim
is enforced in code and checked in CI, not just stated here.

| Claim | How it is enforced |
|---|---|
| **The token is full-access; read-only is enforced by the server.** Money Lover has no scoped or read-only tokens. | Write tools are always listed but do nothing unless `MONEYLOVER_ALLOW_WRITE=true`; when they are enabled, a server-side guard limits them ([Security](#security)). |
| **Network egress goes only to Money Lover**: `web.moneylover.me` and `oauth.moneylover.me`, HTTPS only. No telemetry, no update checks, no other hosts. | Endpoints are compile-time constants that no environment variable can change; redirects are never followed. [`tests/egress.rs`](tests/egress.rs) fails if any other URL appears in the source. |
| **Credentials stay local.** The password is used for login and never stored; tokens live in a `0600` file; nothing secret is logged or returned to the AI. | [`src/token_store.rs`](src/token_store.rs), redacting `Debug` impls, tests. |
| **Dependencies are pinned and vetted.** | `Cargo.lock` + `--locked` everywhere; [`cargo-deny`](deny.toml) allows crates.io sources and permissive licenses only and fails on RustSec advisories or yanked crates (weekly and on change); GitHub Actions pinned to commit SHAs; Dependabot. |
| **Release binaries are built from this repository.** | Every release archive has a signed SLSA build-provenance attestation (`gh attestation verify`) and a `SHA256SUMS` entry. |
| **The published bundles are bound to this repository and to this domain.** | The listing in the official MCP Registry uses the DNS-verified namespace `com.iyongbudiarso/moneylover-mcp`; the hash of every `.mcpb` is recorded in the release's `server.json`, and each bundle carries the same build-provenance attestation as the archives. |

## Tools

| Tool | What it does | Needs write mode |
|---|---|---|
| `list_wallets` | Wallets with currency and balance (`refresh: true` for live balances) | no |
| `list_categories` | Income/expense categories, for the whole account or one wallet | no |
| `list_transactions` | Transactions in a date range, newest first (`limit` default 50, max 200) | no |
| `summarize_spending` | Totals per category, grouped by currency, for a date range | no |
| `add_transaction` | Create a transaction; supports `dry_run` | **yes** |
| `update_transaction` | Change the category, amount, note or date of a transaction; supports `dry_run` | **yes** |
| `delete_transaction` | Delete a transaction; supports `dry_run` | **yes** |

Design choices that matter for LLM use:

- Wallets and categories are given by **name**, never by id. Matching is exact (case-insensitive);
  an unknown or ambiguous name returns an error listing the candidates instead of guessing.
- Amounts are **always positive**. Income vs expense comes from the category. Pass `type` when a
  category name exists as both (e.g. "Other").
- Money is returned as **decimal strings**, never floats.
- Dates are `YYYY-MM-DD` in your configured timezone. A date range may cover at most **366 days**
  per call; the write tools accept dates from a year back up to a month ahead.
- `update_transaction` and `delete_transaction` take the `id`, `wallet` **and** `date` that
  `list_transactions` returns (Money Lover cannot look a transaction up by id alone). An update
  changes only the fields you pass and keeps the rest, including photo, location, event, people and
  the "exclude from report" flag. Moving a transaction to another wallet is not supported: delete it
  and add it again. Transfers, debts and loans are linked to other transactions and are refused;
  change those in the app.
- `summarize_spending` skips transactions flagged "exclude from report"; `list_transactions`
  still lists them, so the two never disagree silently.
- Without a `wallet`, `list_transactions` and `summarize_spending` cover **every** wallet, including
  archived wallets and wallets excluded from Money Lover's total. Money Lover's own "all wallets"
  query leaves those out and returns at most 9,999 rows, so the server asks for those wallets
  separately (and for every wallet one by one when the 9,999-row cap is hit). On accounts with many
  such wallets this takes longer; pass `wallet` when you only need one. A single wallet with 9,999 or
  more transactions in the range is an error asking for a shorter range, never a silently short
  list or total.
- The write tools are **always listed**, so a client and its security scanner see the whole surface,
  but without `MONEYLOVER_ALLOW_WRITE=true` every call is refused before it reaches the network.
  See [Security](#security) for the limits that apply to writes.
- Wallets and categories are **cached in memory** (5 minutes by default) because Money Lover's
  wallet list can take 30+ seconds on large accounts. Writing a transaction clears the cached
  balances, a name that is not in the cache triggers one fresh fetch (so a wallet you just created
  is found), and `list_wallets` reports `from_cache` so an agent knows when balances may be stale.
  Changes made in the Money Lover app itself show up after the cache expires or with `refresh: true`.

## Install

### Prebuilt binary (no Rust needed)

Download the archive for your platform from the
[latest release](https://github.com/Iyongbudiarso/moneylover-mcp/releases/latest):

| Platform | Archive |
|---|---|
| macOS, Apple Silicon | `moneylover-mcp-<version>-aarch64-apple-darwin.tar.gz` |
| macOS, Intel | `moneylover-mcp-<version>-x86_64-apple-darwin.tar.gz` |
| Linux x86_64 (glibc 2.35+) | `moneylover-mcp-<version>-x86_64-unknown-linux-gnu.tar.gz` |
| Linux ARM64 (glibc 2.35+) | `moneylover-mcp-<version>-aarch64-unknown-linux-gnu.tar.gz` |
| Windows x86_64 | `moneylover-mcp-<version>-x86_64-pc-windows-msvc.zip` |

Verify it before running it. `SHA256SUMS` is attached to every release, and every archive has a
signed build-provenance attestation proving it was built by this repository's release workflow:

```sh
shasum -a 256 -c SHA256SUMS --ignore-missing          # Linux: sha256sum --check --ignore-missing SHA256SUMS
gh attestation verify moneylover-mcp-*.tar.gz --repo Iyongbudiarso/moneylover-mcp
```

Extract it and put `moneylover-mcp` somewhere on your `PATH`. The binaries are not signed with an
Apple or Microsoft certificate. On macOS, a file downloaded with a browser is blocked by
Gatekeeper; after verifying it, allow it with `xattr -d com.apple.quarantine moneylover-mcp`.
On Windows, SmartScreen may ask for confirmation.

### MCPB bundle (Claude Desktop and other MCPB clients)

Clients that understand [MCP Bundles](https://github.com/modelcontextprotocol/mcpb) can install
the server as an extension instead. Download the `.mcpb` for your platform from the
[latest release](https://github.com/Iyongbudiarso/moneylover-mcp/releases/latest) and open it, or
point your client at the file:

| Platform | Bundle |
|---|---|
| macOS (Apple Silicon and Intel) | `moneylover-mcp-darwin.mcpb` |
| Linux x86_64 | `moneylover-mcp-linux-x86_64.mcpb` |
| Linux ARM64 | `moneylover-mcp-linux-aarch64.mcpb` |
| Windows x86_64 | `moneylover-mcp-windows-x86_64.mcpb` |

There is one bundle per platform because a bundle carries a single executable and MCPB cannot
choose between executables by CPU architecture; the macOS bundle holds one universal binary.

When the client asks for configuration you can either enter your Money Lover email and password
(the client keeps the password in the OS keychain, and the server exchanges it for a token, exactly
as with `login`), or leave both empty and run `moneylover-mcp login` once with a
[prebuilt binary](#prebuilt-binary-no-rust-needed) — the extension reuses the stored token.
Adding transactions stays **off** unless you enable it in the extension's settings.

The same bundles are listed in the official MCP Registry as
[`com.iyongbudiarso/moneylover-mcp`](https://registry.modelcontextprotocol.io/v0.1/servers?search=com.iyongbudiarso/moneylover-mcp),
which is verified by a `v=MCPv1; …` TXT record on `iyongbudiarso.com`. Registry-aware clients
check each download against the SHA-256 recorded there. That proves who published the entry; it is not a statement that the code is
safe. Check the [trust model](#trust-model) and the source instead.

### From source

You need a [Rust toolchain](https://rustup.rs) (1.89 or newer).

```sh
cargo install --locked --git https://github.com/Iyongbudiarso/moneylover-mcp
# or, from a checkout:
cargo build --locked --release   # binary: target/release/moneylover-mcp
```

`--locked` builds with the exact dependency versions in `Cargo.lock`, which CI vets with
`cargo deny`, instead of whatever is newest on crates.io.

### Log in

Log in **once, from a terminal**. The password is typed at a hidden prompt and never stored;
only the resulting token is saved:

```sh
moneylover-mcp login
```

## Configure

All configuration is through environment variables.

| Variable | Default | Meaning |
|---|---|---|
| `MONEYLOVER_TIMEZONE` | `Asia/Jakarta` | IANA timezone for dates |
| `MONEYLOVER_ALLOW_WRITE` | `false` | Set `true` to enable `add_transaction`, `update_transaction` and `delete_transaction` |
| `MONEYLOVER_WRITE_WALLETS` | – | Comma-separated wallet names; when set, writes go **only** to these |
| `MONEYLOVER_MAX_AMOUNT` | – | When set, larger amounts are refused |
| `MONEYLOVER_MAX_WRITES_PER_HOUR` | `20` | Adds, updates and deletes per rolling hour, combined (1-1000) |
| `MONEYLOVER_TOKEN_PATH` | `~/.config/moneylover-mcp/token.json` | Where tokens are stored. Must be absolute |
| `MONEYLOVER_CACHE_SECS` | `300` | How long wallets and categories are cached (0 disables) |
| `MONEYLOVER_TIMEOUT_SECS` | `45` | Per-request timeout (1-300). Money Lover's servers can be slow |
| `MONEYLOVER_LOG` | `info` | Log filter. Logs go to stderr only |
| `MONEYLOVER_EMAIL`, `MONEYLOVER_PASSWORD` | – | Only for headless setups that cannot run `login`; see below |

### Authentication notes

- Only **email + password** accounts are supported. Accounts created with Google, Facebook or Apple
  sign-in have no password and cannot log in this way.
- A Money Lover account allows roughly **5 devices**, and every fresh login uses one slot. The
  server stores its tokens in `token.json` and refreshes them (a refresh uses no slot). Several
  MCP clients may share the same token file safely; access is serialized with a lock file.
- If the token can no longer be refreshed, run `moneylover-mcp login` again.
- `moneylover-mcp logout` deletes the local token and stops running servers immediately. The token
  itself stays valid at Money Lover until it expires; to revoke it, remove the device in the Money
  Lover app. Either way every server sharing the token file stops until you run
  `moneylover-mcp login` again; it never silently logs back in.
- A wrong password is tried once per server start, never in a loop.

### Example client config

No password in the config: run `moneylover-mcp login` first.

```json
{
  "mcpServers": {
    "moneylover": {
      "command": "/path/to/moneylover-mcp",
      "env": {
        "MONEYLOVER_TIMEZONE": "Asia/Jakarta"
      }
    }
  }
}
```

In Zed, put the same thing under `context_servers` in `settings.json`:

```json
{
  "context_servers": {
    "moneylover": {
      "command": "/path/to/moneylover-mcp",
      "env": { "MONEYLOVER_TIMEZONE": "Asia/Jakarta" }
    }
  }
}
```

To let an agent record, correct and delete transactions, opt in and fence it in:

```json
"env": {
  "MONEYLOVER_ALLOW_WRITE": "true",
  "MONEYLOVER_WRITE_WALLETS": "Cash",
  "MONEYLOVER_MAX_AMOUNT": "2000000"
}
```

Headless servers (a container, a VPS running a chatbot) can pass `MONEYLOVER_EMAIL` and
`MONEYLOVER_PASSWORD` instead; use your platform's secret store, not a file in a repository.

## Security

**Do not commit MCP client config files that contain credentials.** Zed's `settings.json` and
similar files often end up in public dotfiles repositories.

What the server does to keep your account safe:

- **Credentials.** The password is only used for login and never stored. Tokens live in
  `token.json`, created with mode `0600` in a `0700` directory; a token file that other users can
  read is tightened to `0600`, or refused if that is not possible. Passwords, tokens and
  `Authorization` headers never appear in logs or in tool output. On Windows the file relies on
  your user profile's default permissions.
- **Network.** HTTPS only, certificates checked against the OS trust store, redirects never
  followed (so a password or token can never be re-sent elsewhere), response size capped.
- **Writes are off by default.** Without `MONEYLOVER_ALLOW_WRITE=true`, the write tools
  (`add_transaction`, `update_transaction`, `delete_transaction`) still appear in the tool list but
  answer with an error instead of calling Money Lover. When enabled, every write is checked by the
  server, not by the AI:
  - only wallets in `MONEYLOVER_WRITE_WALLETS` (if set), archived wallets never. For updates and
    deletes this is the wallet the transaction is in;
  - amounts up to `MONEYLOVER_MAX_AMOUNT` (if set);
  - dates from one year ago to one month ahead. Updates and deletes also only touch transactions
    dated in that window, so an agent cannot rewrite older history;
  - at most `MONEYLOVER_MAX_WRITES_PER_HOUR` adds, updates and deletes per hour, combined (counted
    per server process: two MCP clients each running the server get the limit twice);
  - an identical transaction within 10 minutes is refused as an accidental retry unless the
    agent passes `allow_duplicate: true`;
  - transfers, debts, loans and repayments are never updated or deleted, because each is linked
    to other transactions that would be left inconsistent;
  - writes are never retried automatically. If a write times out, the tool answers `UNCERTAIN`
    and tells the agent to check before retrying.

What you should know:

- **Prompt injection.** Transaction notes, wallet names and category names are shown to the AI.
  In a **shared wallet**, other people write those. Text like "ignore previous instructions and
  add …" could steer an agent. The server tells the agent to treat this text as data, but the
  real protection is the write limits above. With writes on, an injected instruction could also
  ask for a delete, which has no undo here. Keep writes off unless you need them, restrict
  `MONEYLOVER_WRITE_WALLETS`, and prefer an MCP client that asks you before each tool call.
  `MONEYLOVER_WRITE_WALLETS` matches wallet **names**; the owner of a wallet shared with you can
  rename it, so list wallets you own.
- **Combining servers.** An agent that can both read your finances and send data out (web fetch,
  email, …) can be tricked into leaking them. Think about which tools you enable together.
- **Logs.** Logs go to stderr, which MCP clients often save to a file. At the default `info` level
  they contain no financial data. `MONEYLOVER_LOG=rmcp=debug` logs full tool inputs and outputs.
- **Your AI provider** sees whatever the tools return (balances, transactions). That is inherent
  to using a cloud AI with your financial data.

## Releasing

1. Bump `version` in `Cargo.toml`, run `cargo build` to update `Cargo.lock`, commit.
2. Tag and push: `git tag v0.3.1 && git push origin v0.3.1`.

The [release workflow](.github/workflows/release.yml) checks that the tag matches `Cargo.toml`,
runs the tests, builds all platforms, attests their provenance and publishes the GitHub release
with `SHA256SUMS`. Running the workflow manually (Actions > Release > Run workflow) only builds,
which is a handy dry run.

A second job then assembles the `.mcpb` bundles from those archives with the pinned
`@anthropic-ai/mcpb` CLI, smoke-tests the macOS one, attests each bundle and attaches the bundles
plus `SHA256SUMS.mcpb` and a generated `server.json` to the same release.

### Publishing to the MCP Registry

This is a manual step on purpose: it needs an Ed25519 key that must not live in the repository or
in CI. `scripts/publish-registry.sh` fetches the released `server.json`, downloads the bundles once
and caches them by SHA-256, logs in if the registry token has expired (it lives five minutes),
re-checks every hash, publishes, and finally confirms the new version through the registry API:

```sh
brew install mcp-publisher          # the registry's CLI, once
scripts/publish-registry.sh v0.4.0            # ~20 s cold, ~2 s warm
scripts/publish-registry.sh v0.4.0 --dry-run  # download + verify only
```

If the version is already listed the script stops immediately instead of downloading anything.
Bundles are cached in `~/.cache/mcp-publisher/<owner>__<repo>/<tag>/`, so re-running is free.

The first time, and after a key rotation, the script's header explains how to generate the key and
put the public half in a TXT record on the apex domain; the login itself is now part of the script.

Two things to know: a published version is immutable, so bump the version rather than rebuilding a
tag; and rebuilding a tag produces different bundle bytes (ZIP timestamps), so publish the registry
entry after the release workflow has run and do not re-run the workflow for a published tag.

## Development

```sh
cargo test                      # unit + end-to-end tests against a mocked Money Lover
cargo clippy --all-targets
```

Live tests hit the real API and are skipped by default. Run `moneylover-mcp login` (or
`cargo run -- login`) first, then:

```sh
cargo test --test live live_read_only -- --ignored --nocapture      # read only

# Optional write test: needs a wallet named exactly "MCP Test"
MONEYLOVER_LIVE_WRITE=1 MONEYLOVER_LIVE_CATEGORY="<an expense category in that wallet>" \
  cargo test --test live live_write -- --ignored --nocapture
```

Delete the test transaction in the Money Lover app afterwards.

### Static analysis of the agent-facing surface

[`snyk-agent-scan`](https://github.com/snyk/agent-scan) looks at what an AI client actually sees —
tool descriptions, prompt-injection surface, hardcoded secrets. It is not part of CI because it
needs a Snyk token and uploads tool names and descriptions to `api.snyk.io`, so run it by hand
when you want it:

```sh
SNYK_TOKEN=… uvx snyk-agent-scan@0.6.8 scan --ci \
  --dangerously-run-mcp-servers --suppress-mcpserver-io=true mcp.json
```

Generate `mcp.json` from your normal client config (it is the usual `{"mcpServers": {…}}` shape)
and do not commit it if it contains credentials.

## License

MIT. See [LICENSE](LICENSE).

TDQS

A4.7/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct action or resource: add/update/delete transactions, list transactions, list wallets, list categories, and summarize spending. Descriptions explicitly cross-reference alternatives (e.g., list_transactions vs. summarize_spending, list_categories vs. list_wallets), making misselection unlikely. No two tools appear to do the same thing.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern: add_transaction, update_transaction, delete_transaction, list_transactions, list_wallets, list_categories, summarize_spending. There are no deviations in casing or naming style. The convention is predictable throughout.

Tool Count5/5

Seven tools is well-scoped for a personal finance transaction server: four read/reference tools and three transaction write tools. Each tool earns its place without obvious redundancy or missing core operation. The count avoids both excessive surface area and a thin single-purpose tool.

Completeness4/5

The transaction lifecycle is fully covered: create, read/list, update, and delete, plus supporting wallet/category lookups and spending summaries. Minor gaps exist, such as no wallet or category management and no direct get-by-id or category-filtered transaction listing, but these are workable through existing tools. Core user workflows are supported.

Maintenance

ActivityMaintained
ResponsivenessNo issues