Skip to main content
Glama
udaysrinu

ExpensifyAI

by udaysrinu
README.md
# ExpensifyAI

**Talk to your Splitwise. Get CRED-grade spending analytics.**

ExpensifyAI is a Model Context Protocol (MCP) server for [Splitwise](https://www.splitwise.com/)
that lets any LLM client (Claude, etc.) manage shared expenses **and** produce deterministic,
premium spending analytics — category breakdowns, monthly trends, per-member comparisons, and
minimum-transaction settlement plans — rendered as a self-contained, offline HTML dashboard.

> Built on top of the excellent [tarunn2799/splitwise-mcp](https://github.com/tarunn2799/splitwise-mcp);
> extended with a deterministic analytics engine and a category-first dashboard.

![ExpensifyAI dashboard](docs/screenshots/dashboard.png)

*Interactive dashboard, generated from synthetic data. Open `examples/demo-dashboard.html`
in a browser to try it live — pick a date range and every section recomputes instantly.*

## Analytics (what makes this ExpensifyAI)

- **Deterministic by construction** — every number is computed in pure Python with `Decimal`
  math (no float drift, no LLM estimation). Same input → byte-identical output. Each report
  carries a **reconciliation check**: per-expense shares must sum to cost, or the mismatch is flagged.
- **Category-first, à la CRED** — an expandable "where it goes" view leads every report; tap a
  category to drill into its transactions.
- **Seven analytics modules** — category breakdown · monthly trend · owed-vs-paid ("mine vs split")
  · per-member comparison + category×member matrix · transaction ledger · top transactions ·
  **settlement optimizer** (minimum transactions to settle a group — no other Splitwise tool has this).
- **Interactive dashboard** — CRED-grade dark UI with a live date-range picker + presets
  (this month / 3mo / 6mo / this year / all) that re-filter and recompute every section in the
  browser. Hand-rolled inline-SVG charts, validated colorblind-safe palette, fully offline
  (self-contained single file — no CDN, no server). All client math is integer paise, so the
  live recompute stays exact and reconciles against the Python source of truth.
- **Two analytics tools** — `analyze_spending(target_type, target_id?, dates?, generate_dashboard?)`
  and `compare_group_members(group_id, …)`. `target_type` is `me` | `group` | `friend`.

## Local mirror: delta sync + instant search

Splitwise's own search is Pro-paywalled and the API has no search endpoint. ExpensifyAI
mirrors your data into a local SQLite DB (`~/.expensifyai/splitwise.db`) and searches it offline.

- **`sync_all(full=False)`** — **delta sync**: uses the API's `updated_after` cursor so only
  expenses added/edited/moved/deleted since the last sync are fetched (first run pulls everything;
  a re-sync with no changes is a single call). Upserts by expense id, so it's idempotent. Groups
  and friends are fully refreshed each run (small). Deletes are mirrored (soft-deleted, excluded
  from search by default).
- **`search_expenses(query?, min_amount?, max_amount?, user_id?, group_id?, category?, dates?)`** —
  full-text (FTS5) over description/details/category plus structured filters, against the local DB.
  Instant, offline, covers your entire history across all groups. Beats the paywalled app search.

Each expense's full raw API object is stored (`raw` column) as the source of truth, so displayed
values stay faithful — the indexed REAL columns are only for filtering.

## Itemization, receipt scanning & default splits

Splitwise-Pro-parity, built deterministically:

- **Structured itemization** — `create_itemized_expense(description, group_id, items, …)` turns
  line-items into ONE expense where **each item can split differently** (beers ¾ to one person,
  groceries 4-way, cake between two). Each person's total `owed_share` is computed in exact
  integer paise (largest-remainder rounding, so an indivisible ₹100/3 still sums back to ₹100),
  and the expense is **reconciled to its total before anything is written** — a mismatch refuses
  to create rather than posting a wrong split. `dry_run=True` previews the computed split.
- **Receipt scanning (LLM-vision-native)** — no OCR engine, no cloud keys, no new dependencies:
  the calling agent (Claude) reads the receipt image, extracts line-items, and calls
  `create_itemized_expense`. The server owns the exact math and the Splitwise write.
- **Receipt image upload** — `attach_receipt(expense_id, image_path)` uploads a local
  image/PDF to an existing expense (multipart), so the receipt shows on it in the Splitwise
  app. Pairs with the scan flow: extract line-items from the image, then attach the image itself.
- **Statement import** — `import_statement(transactions, default_split_name?, dedup)` proposes a
  categorized, split-suggested, duplicate-flagged list from parsed statement rows;
  `confirm_import(rows)` bulk-creates the approved ones via the itemization engine.
- **Gmail read-only connector (optional)** — `gmail_find_statements` / `gmail_read_statement`
  fetch bank/card statement emails (scope `gmail.readonly`) to feed statement import — the
  CRED-style "no manual entry" flow. Requires a one-time Google Cloud OAuth setup; install
  extras with `pip install -e ".[gmail]"`. The connector only reads email text; all expense
  creation still goes through the reviewed import path.
- **Save default splits** — `save_default_split(name, split)` / `list_default_splits` /
  `delete_default_split`. Reuse a template by putting `"split_ref": "roomies-4way"` on an item.
  Stored locally in `~/.expensifyai/splits.json`.

Pick any date range — the whole dashboard recomputes live in the browser:

![Filtered to one month](docs/screenshots/filtered.png)

Try it without an account:

```bash
python examples/generate_demo.py   # writes examples/demo-dashboard.html
```

## Features (MCP)

- **Full API Access**: Manage expenses, groups, friends, and comments.
- **Natural Language Resolution**: Fuzzy matching for names ("John" -> "John Smith") and groups.
- **Dual Auth**: Supports both OAuth 2.0 (recommended) and API Keys.
- **Smart Caching**: Optimizes performance for static data like categories and currencies.

## Prerequisite: Splitwise Pro

**Since early September 2026 the Splitwise API requires the API app's registered developer to hold an
active [Splitwise Pro](https://secure.splitwise.com/pro) subscription (~$3/mo, $30/yr).** Without it
every call returns:

```
403 — base: This app can't access Splitwise right now because its developer
      doesn't have an active Splitwise Pro subscription.
```

This is Splitwise's policy, not a bug here — their [API terms](https://dev.splitwise.com/) reserve it
explicitly ("Splitwise may impose conditions on the use of the Self-Serve API, including, for example,
maintaining an active Splitwise Pro subscription"). Verified: the same 403 is returned by this server,
by a bare API client with no server involved, and to keys that worked days earlier. **There is no
workaround** — the gate sits upstream of every endpoint, reads included.

So to use the 37 Splitwise tools you need Pro, then register an app at
[secure.splitwise.com/apps](https://secure.splitwise.com/apps) for your key. The 5 statement tools
(Gmail, PDF unlock, bank store) need no Splitwise account at all — 3 of them work entirely offline.

## Installation

```bash
git clone https://github.com/udaysrinu/ExpensifyAI
cd ExpensifyAI
python -m venv venv
source venv/bin/activate
pip install -e .
```

## Configuration

See [SETUP.md](SETUP.md) for detailed authentication and configuration instructions.

### Quick Config
Run the included setup script:
```
python -m splitwise_mcp_server.oauth_setup
```

Use the keys provided there, and add all three to your `mcp.json`:

```json
{
  "mcpServers": {
    "splitwise": {
      "command": "python",
      "args": ["-m", "splitwise_mcp_server"],
      "env": {
        "SPLITWISE_OAUTH_ACCESS_TOKEN": "your_token_here"
      }
    }
  }
}
```

> **Get your Auth Keys**
> You can get your Consumer Key and Secret by registering an app at [https://secure.splitwise.com/apps](https://secure.splitwise.com/apps).

> **IMPORTANT:** Using a Virtual Environment?
> If you installed the package in a `venv` or Conda environment, you must use the **absolute path** to the python executable in your config.
>
> ```json
> "command": "/absolute/path/to/venv/bin/python"
> ```
> See [SETUP.md](SETUP.md#using-a-virtual-environment-venvconda) for details.

## Usage

The server enables natural language interactions with your Splitwise data.

**Examples:**
- "What's my current balance?"
- "Split a $50 dinner with Sarah."
- "Use the receipt I uploaded to split the dinner between Manav and me."
- "Show me expenses from last month."
- "Create a group called 'Ski Trip' with Mike."

## Hosted mode — use it from your phone, with your own key

The stdio server above runs as a subprocess on your machine, which the Claude mobile and web apps
cannot do. `api/index.py` is the same server over HTTP, deployable to Vercel and addable as a custom
connector.

**You bring your own key; the server stores nothing.** Add the connector with your Splitwise API key
(from [secure.splitwise.com/apps](https://secure.splitwise.com/apps)) as a bearer token:

```
URL:    https://<your-deployment>.vercel.app
Header: Authorization: Bearer <your Splitwise API key>
```

Each request is served with the credential it arrives with, so one deployment works for any number of
people, and the operator never holds anyone else's key. **Do not set `SPLITWISE_API_KEY` on a shared
deployment** — it becomes the fallback for keyless requests, which turns the public URL into your own
account. See [`per_request.py`](src/splitwise_mcp_server/per_request.py).

**The hosted server has 37 of the 42 tools.** Left out: `gmail_find_statements`,
`gmail_read_statement`, `unlock_statement_pdf`, `import_statement`, `confirm_import`. Those need your
Gmail token, your statement PDFs and the SQLite store under `~/.expensifyai`. A serverless filesystem
is ephemeral so they could not work anyway — and hosting them would put your bank statements and
mailbox credentials on a server, which defeats the point of keeping that data local. Bank statement
import stays on stdio.

```bash
vercel            # deploy; no environment variables to set
```

Because a key is tied to the app that issued it, bring-your-own-key means each user registers their own
app — and so each needs their own Pro (see [Prerequisite](#prerequisite-splitwise-pro)). If you would
rather carry that cost yourself so your users pay nothing, switch this to **OAuth2**: one app you
register, your Pro, and users just authorize. `OAuth2Handler` already emits the same
`Authorization: Bearer <token>` header, so `per_request.py` needs no changes — only the token's source
does. `oauth_setup.py` has the authorization-code flow.

## Tools

See [TOOLS.md](TOOLS.md) for detailed documentation.

### User Tools
- `get-current-user`: Get authenticated user information
- `get-user`: Get information about a specific user

### Expense Tools
- `create-expense`: Create a new expense with splits
- `get-expenses`: List expenses with optional filters
- `get-expense`: Get detailed expense information
- `update-expense`: Update an existing expense
- `delete-expense`: Delete an expense

### Group Tools
- `get-groups`: List all groups
- `get-group`: Get detailed group information
- `create-group`: Create a new group
- `delete-group`: Delete a group
- `add-user-to-group`: Add a user to a group
- `remove-user-from-group`: Remove a user from a group

### Friend Tools
- `get-friends`: List all friends
- `get-friend`: Get detailed friend information

### Resolution Tools
- `resolve-friend`: Fuzzy match friend names to user IDs
- `resolve-group`: Fuzzy match group names to group IDs
- `resolve-category`: Fuzzy match category names to category IDs

### Comment Tools
- `create-comment`: Add a comment to an expense
- `get-comments`: Get all comments for an expense
- `delete-comment`: Delete a comment

### Utility Tools
- `get-categories`: Get all expense categories
- `get-currencies`: Get all supported currencies

### Arithmetic Tools
- `add`: Add multiple numbers
- `subtract`: Subtract numbers
- `multiply`: Multiply numbers
- `divide`: Divide numbers
- `modulo`: Calculate remainder

## Development

```bash
# Setup
git clone https://github.com/udaysrinu/ExpensifyAI
cd ExpensifyAI
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"

# Test
pytest                              # full suite
pytest tests/test_analytics.py      # deterministic analytics (15 tests)

# See the dashboard with no account
python examples/generate_demo.py    # writes examples/demo-dashboard.html
```

## License

MIT License. See [LICENSE](LICENSE) for details.

Built on top of [tarunn2799/splitwise-mcp](https://github.com/tarunn2799/splitwise-mcp) (MIT);
the analytics engine, interactive dashboard, and tests are added by ExpensifyAI.

TDQS

A3.6/5.0

Scored across 42 tools

Disambiguation4/5

Most tools have clear distinct purposes (expenses, groups, friends, splits, sync/search, import pipeline). Some potential confusion exists between get_expenses/search_expenses/search_notes and analyze_spending/compare_group_members, but descriptions clarify the differences.

Naming Consistency4/5

Tool names mostly follow a consistent verb_noun pattern (create_expense, get_expense, update_expense, delete_expense, restore_expense). Minor deviations: sync_all, analyze_spending, compare_group_members, and the gmail_* / unlock_statement_pdf tools break the pattern slightly but remain readable.

Tool Count3/5

42 tools is on the heavy side for a single server, but the breadth is justified by the domain (expense management, groups, friends, itemization, import pipeline, sync/search). Still, the count feels high and could overwhelm an agent.

Completeness5/5

The tool surface covers the full expense lifecycle (create, read, update, delete, restore), groups, friends, comments, categories, currencies, notifications, itemized expenses, default splits, receipt attachment, local sync/search, and a full statement import pipeline (Gmail → PDF unlock → import → confirm). No obvious dead ends.

Maintenance

ActivityMaintained
ResponsivenessNo issues