Metadata-Version: 2.5
Name: zenmoney-mcp-server
Version: 0.5.0
Summary: MCP server for trustworthy ZenMoney analytics and confirmed user-entity changes
Project-URL: Repository, https://github.com/ekho/zenmoney-mcp
Project-URL: Issues, https://github.com/ekho/zenmoney-mcp/issues
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp<3,>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# ZenMoney MCP Server

MCP server for trustworthy personal-finance analytics and explicitly confirmed
user-entity changes over the [ZenMoney](https://zenmoney.ru/) API. The project
started as a fork of [nnslvp/zenmoney-mcp](https://github.com/nnslvp/zenmoney-mcp)
and is maintained here as a substantially extended version. It keeps its working
data local and adds financially conservative calculations, atomic sync, and a
two-step write workflow.

## Complete tool catalog

Both local and remote modes expose 56 tools. They share 54 tools and use two
mode-specific tools for synchronization and category suggestions.

| Area | Tools |
|---|---|
| Financial analytics | `get_net_worth`, `get_liquidity`, `analyze_spending`, `analyze_income`, `analyze_merchants`, `check_budget_health`, `get_upcoming_payments`, `analyze_trends`, `detect_recurring`, `get_account_flow`, `analyze_transfers`, `detect_anomalies`, `get_debts`, `convert_currency`, `get_exchange_rates`, `search_transactions` |
| Planning analytics | `get_financial_snapshot`, `get_cash_flow`, `get_spending_baseline`, `compare_periods`, `get_emergency_fund_status`, `get_debt_service`, `forecast_cash_flow` |
| Decision support | `plan_emergency_fund`, `plan_debt_payoff`, `compare_debt_strategies`, `plan_financial_goal`, `plan_multiple_goals`, `run_financial_scenario`, `build_financial_plan` |
| Entity reads | `list_accounts`, `get_account`, `list_tags`, `get_tag`, `list_merchants`, `get_merchant`, `list_reminders`, `get_reminder`, `list_reminder_markers`, `get_reminder_marker`, `list_transactions`, `get_transaction`, `list_budgets`, `get_budget` |
| Confirmed entity changes | `prepare_account_changes`, `prepare_tag_changes`, `prepare_merchant_changes`, `prepare_reminder_changes`, `prepare_reminder_marker_changes`, `prepare_transaction_changes`, `prepare_budget_changes`, `prepare_mixed_changes`, `get_change_proposal`, `apply_changes` |

| Mode | Mode-specific tools |
|---|---|
| Local stdio | `sync_data`, `suggest_category` |
| Remote through OpenAI Secure MCP Tunnel | `force_sync`, `get_sync_status` |

## Analytics

| Question | Tool |
|---|---|
| How much money do I have? | `get_net_worth` |
| Can I afford a purchase? | `get_liquidity` |
| Where does my money go? | `analyze_spending` |
| Where does my income come from? | `analyze_income` |
| Which merchants receive my money? | `analyze_merchants` |
| Am I within budget? | `check_budget_health` |
| What subscriptions do I have? | `detect_recurring` |
| How are income and spending changing? | `analyze_trends` |
| What transfers did I make? | `analyze_transfers` |
| Are there unusual or duplicate expenses? | `detect_anomalies` |
| Who owes whom? | `get_debts` |
| What payments are coming up? | `get_upcoming_payments` |
| Find matching transactions | `search_transactions` |
| What happened on this account? | `get_account_flow` |
| Convert currencies | `convert_currency`, `get_exchange_rates` |
| Overall financial position | `get_financial_snapshot` |
| Monthly cash flow | `get_cash_flow` |
| Normal spending level | `get_spending_baseline` |
| Compare periods | `compare_periods` |
| Emergency fund coverage | `get_emergency_fund_status` |
| Debt burden | `get_debt_service` |
| 30/60/90 day forecast | `forecast_cash_flow` |

The server also exposes paginated collection and exact resources for Account,
Tag, Merchant, Reminder, ReminderMarker, Transaction, and Budget, plus currencies,
synchronization status, and a cache-only financial snapshot at
`zenmoney://financial-snapshot`. Reading a resource never starts synchronization.

## Confirmed user-entity changes

All writes use two separate calls. Choose the entity-specific prepare tool for
ordinary work, or `prepare_mixed_changes` when one proposal creates or changes
several related entity types:

```text
prepare_account_changes        prepare_tag_changes
prepare_merchant_changes       prepare_reminder_changes
prepare_reminder_marker_changes
prepare_transaction_changes    prepare_budget_changes
prepare_mixed_changes
get_change_proposal            apply_changes
```

Prepare validates 1–100 operations and returns an immutable field-by-field
preview without writing to ZenMoney. After reviewing it, pass only its
`proposal_id` to `apply_changes`; `get_change_proposal` reports state and results.

Preparation requires a successful full sync so that untouched ZenMoney fields
can be preserved. Apply rejects the whole proposal before writing if any source
entity changed since preparation. Related creates are submitted in dependency
layers because the live API does not safely accept every dependency in one Diff
request; a failed layer is never retried or rolled back automatically.

Create and update are supported for all seven user entities. Safe delete archives
an Account, marks a Transaction or ReminderMarker deleted, or clears a Budget.
Tag, Merchant, and Reminder deletion and all physical purge operations are not
exposed. Prepared proposals expire after 24 hours. Terminal proposals are retained
for 30 days, and an uncertain write or verification result becomes `needs_review`.

Planning analytics are deliberately conservative:

- emergency-fund coverage requires explicit essential category IDs or a monthly
  essential-spending override;
- debt service reports observed balances and payments but does not infer APR,
  minimum payments, or amortization schedules;
- recurring-payment detection is a historical heuristic and is labeled as such;
- cash-flow forecasts are transparent scenarios, not prediction guarantees.

## Financial Planning

Phase 3 adds deterministic decision support on top of the factual analytics.
Every result exposes inputs, assumptions, constraints, reasons, alternatives,
and measurable outcomes; it does not execute or write a financial decision.

| Question | Tool |
|---|---|
| How fast can I build a 6-month emergency fund? | `plan_emergency_fund` |
| Should I pay the high-interest loan first? | `plan_debt_payoff`, `compare_debt_strategies` |
| Can I afford a car in 18 months? | `plan_financial_goal` |
| Which of my goals conflict? | `plan_multiple_goals` |
| What happens if my income falls by 20%? | `run_financial_scenario` |
| How should I allocate my monthly free cash flow? | `build_financial_plan` |

Planning inputs that ZenMoney does not contain must be supplied explicitly:

```json
{
  "emergency_fund": {
    "target_months": 6,
    "essential_category_ids": ["category-id"]
  },
  "debt_accounts": {
    "loan-account-id": {
      "apr_pct": 19.9,
      "minimum_payment": 15000
    }
  },
  "goals": []
}
```

Missing APR, minimum payments, or essential-spending configuration returns
`configuration_required`; the server never invents those values. Calculations
use zero investment return, Decimal money arithmetic, and future calendar
month-end snapshots. Restricted deposits are excluded from emergency reserves
unless explicitly enabled, and credit capacity is always excluded.

See [`docs/planning-semantics.md`](docs/planning-semantics.md) for the priority
policy, formulas, rounding, data-quality labels, and limitations.

## Runtime modes

The installed `zenmoney-mcp` command is the local stdio server for Codex,
ChatGPT Desktop, Claude Desktop, and Cursor. It uses the shared SDK v2
registry and hardened runtime directly; it does not preserve an upstream
server through a runtime overlay.

For a private remote deployment, `zenmoney-mcp-http` exposes Streamable HTTP
at `/mcp` only inside Docker, and the OpenAI Secure MCP Tunnel client connects
outbound to OpenAI. The remote registry excludes the local API-dependent
`sync_data` and `suggest_category` tools. Its analytical tools remain read-only.
Remote `force_sync` can request a cache refresh, while confirmed entity-change
proposals are queued for the separate credentialed worker. `get_sync_status`
and `get_change_proposal` report their respective progress. The MCP
container still receives no ZenMoney token and cannot write the financial
snapshot or call ZenMoney directly. See the
[remote operations runbook](deploy/remote-mcp/README.md) and
[threat model](docs/remote-mcp-threat-model.md).

## Hardening in this fork

The installed `zenmoney-mcp` command runs `zenmoney_mcp.entrypoint` against a
shared SDK v2 registry with hardened database, synchronization, and analytics
implementations:

- `HardenedDatabase` adds idempotent migrations and strict FX handling;
- `HardenedSyncEngine` validates responses and atomically replaces the live cache;
- corrected analytics cover net worth, liquidity, budgets, debts, account flow,
  spending, transaction search, upcoming payments, and FX;
- remaining analytics receive bounded runtime validation;
- MCP discovery advertises the same limits enforced at runtime.

Key semantics:

- `net_worth` includes only active accounts with `in_balance=true`;
- excluded accounts are returned separately in `net_worth_all_accounts`;
- credit is borrowing capacity, not an asset;
- accessible savings and term deposits are not treated as equivalent liquidity;
- budget periods respect the user's configured month-start day;
- zero-budget and unbudgeted spending are surfaced explicitly;
- debt-account balances are authoritative and attribution gaps remain visible;
- account flow includes signed transfers in native and user currency;
- missing or zero exchange rates fail explicitly instead of becoming a 1:1 rate;
- full sync replaces the cache, preventing stale rows from surviving.

More detail is available in
[`README-HARDENING.md`](README-HARDENING.md).

## Local installation with uvx

Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then run
the server from PyPI:

Get a personal API token at [zerro.app/token](https://zerro.app/token), as
documented in the [official ZenMoney API wiki](https://github.com/zenmoney/ZenPlugins/wiki/ZenMoney-API).

```bash
export ZENMONEY_TOKEN="replace-with-your-token"
uvx --from zenmoney-mcp-server zenmoney-mcp
```

`uvx` downloads the package into an isolated cached environment; cloning the
repository or creating a virtual environment is not required.

To run the current `main` branch before its next PyPI release, use
`uvx --from git+https://github.com/ekho/zenmoney-mcp.git zenmoney-mcp`.

The first hardened start performs additive SQLite migrations. Back up
`~/.cache/zenmoney-mcp/zenmoney.db` before the first run when preserving an
existing cache matters. A full sync can recreate the cache from ZenMoney.

## Private ChatGPT installation with OpenAI Secure MCP Tunnel

ChatGPT web cannot start the local stdio command. For private remote access,
run the included Docker Compose deployment and connect it through
[OpenAI Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels).
The MCP endpoint stays inside the Docker network; only the tunnel client makes
an outbound connection to OpenAI.

This mode requires Docker Engine with Compose v2, a ZenMoney token, an OpenAI
tunnel runtime API key, and a tunnel ID associated with the target ChatGPT
workspace. Clone the repository and create the non-secret environment file:

```bash
git clone https://github.com/ekho/zenmoney-mcp.git ~/zenmoney-mcp
cd ~/zenmoney-mcp
cp deploy/remote-mcp/.env.example deploy/remote-mcp/.env
```

Set `CONTROL_PLANE_TUNNEL_ID` in `deploy/remote-mcp/.env`. Provision the
ZenMoney token and OpenAI key as separate file-backed Compose secrets using the
ownership and permission commands in the
[remote operations runbook](deploy/remote-mcp/README.md); do not put either
secret in `.env`. Then pull and start the deployment:

```bash
docker compose --env-file deploy/remote-mcp/.env \
  -f deploy/remote-mcp/compose.yaml pull
docker compose --env-file deploy/remote-mcp/.env \
  -f deploy/remote-mcp/compose.yaml up -d --no-build --pull never
docker compose --env-file deploy/remote-mcp/.env \
  -f deploy/remote-mcp/compose.yaml ps
```

Complete the health checks and `tunnel-client doctor` from the runbook, then add
the MCP app in ChatGPT Developer Mode with **Connection = Tunnel** and scan its
tools.

## ChatGPT Desktop and Codex

Add the server to `~/.codex/config.toml`:

```toml
[mcp_servers.zenmoney]
command = "uvx"
args = ["--from", "git+https://github.com/ekho/zenmoney-mcp.git", "zenmoney-mcp"]
env_vars = ["ZENMONEY_TOKEN"]
tool_timeout_sec = 120
```

Restart the desktop client after changing MCP configuration. For ChatGPT web,
use the private remote Streamable HTTP + Secure MCP Tunnel deployment in the
[operations runbook](deploy/remote-mcp/README.md), not a local executable.

## Claude Desktop or Cursor

```json
{
  "mcpServers": {
    "zenmoney": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/ekho/zenmoney-mcp.git",
        "zenmoney-mcp"
      ],
      "env": {
        "ZENMONEY_TOKEN": "replace-with-your-token"
      }
    }
  }
}
```

## Development

Clone the repository only when changing or testing the code locally:

```bash
git clone https://github.com/ekho/zenmoney-mcp.git ~/zenmoney-mcp
cd ~/zenmoney-mcp
uv sync --extra dev
```

## Data flow

1. Local `sync_data` reads `/v8/diff/` directly through the local sync engine.
2. In the remote deployment, the periodic worker reads `/v8/diff/`; remote
   `force_sync` only asks that credentialed worker to run immediately.
3. Both modes publish a SQLite cache at `~/.cache/zenmoney-mcp/zenmoney.db` or
   the configured `ZENMONEY_DB_PATH`; analytics read that cache locally.
4. A local confirmed proposal is written synchronously. A remote confirmed
   proposal is persisted on the control volume and written by the worker.
5. Only the local process or credentialed worker can call ZenMoney.

## Testing

```bash
uv sync --extra dev
uv run python -m compileall -q src tests
uv run python -m pytest tests/ -v --ignore=tests/test_integration.py
```

The live integration test requires `ZENMONEY_TOKEN` and is excluded from CI.

## License

MIT
