Skip to main content

splitwise-mcp

A Model Context Protocol server that gives any MCP-compatible AI host full, safe control over a Splitwise account — expenses, groups, friends, and balances — with guards against silent duplicate charges and unconfirmed deletions.

PyPI version CI License: MIT

Quickstart

No install step is required. Any MCP host that can launch a command over stdio can run the server directly with uv:

uvx --from splitwise-mcp-server splitwise-mcp

Set SPLITWISE_API_KEY in the environment first (see below), then point your MCP host at that command. Configuration snippets for three hosts are below.

Getting your Splitwise API key

Most setups only need a personal API key:

  1. Go to secure.splitwise.com/apps and register an application (any name and description work).
  2. Copy the generated API key.
  3. Set it as SPLITWISE_API_KEY wherever you configure the server.

A personal API key authenticates as the Splitwise account that generated it — this is all you need for a single-user setup, such as Claude Code or Gemini CLI running on your own machine.

Reach for OAuth2 instead when one deployment of this server needs to act on behalf of someone else's Splitwise account — for example, a shared bot or chat-bridge host serving multiple people, where you don't want each user handing their API key to the operator. To use it:

  1. Register an application at the same secure.splitwise.com/apps page and note its consumer key and secret.
  2. Set that application's Redirect URI / Callback URL to http://127.0.0.1:61438/callback. splitwise-mcp-auth always binds its local callback server to this exact host and port, so it must match what's registered here or Splitwise will refuse the authorization request.
  3. Set SPLITWISE_CONSUMER_KEY and SPLITWISE_CONSUMER_SECRET in the environment.
  4. Run splitwise-mcp-auth once per account. It opens a browser to Splitwise's authorization page, receives the redirect on a local server listening at 127.0.0.1:61438, and caches the resulting access token to ~/.splitwise-mcp/token.json (mode 0600). The MCP server picks that cached token up automatically on every subsequent run. If port 61438 is already in use by something else, free it and run the command again -- it will not fall back to a different port.

Configuring your MCP host

All three snippets below use the same command and the personal-API-key path. Substitute your own key.

Claude Code (.mcp.json)

{
  "mcpServers": {
    "splitwise": {
      "type": "stdio",
      "command": "uvx",
      "args": ["--from", "splitwise-mcp-server", "splitwise-mcp"],
      "env": {
        "SPLITWISE_API_KEY": "your-splitwise-api-key"
      }
    }
  }
}

Gemini CLI (.gemini/settings.json or ~/.gemini/settings.json)

{
  "mcpServers": {
    "splitwise": {
      "command": "uvx",
      "args": ["--from", "splitwise-mcp-server", "splitwise-mcp"],
      "env": {
        "SPLITWISE_API_KEY": "your-splitwise-api-key"
      }
    }
  }
}

Hermes Agent (~/.hermes/config.yaml)

mcp_servers:
  splitwise:
    command: "uvx"
    args: ["--from", "splitwise-mcp-server", "splitwise-mcp"]
    env:
      SPLITWISE_API_KEY: "your-splitwise-api-key"

Worked example: a low-trust registration for chat channels

Some MCP hosts bridge a chat surface you don't fully control — a WhatsApp number shared with roommates, a family group chat, a support inbox — through Hermes Agent or a similar tool. Messages arriving over that channel may come from anyone in the chat, may be routed through a smaller/cheaper model with weaker judgment, and may not even reach a client that implements MCP elicitation (many chat-bridge hosts don't), which means this server's confirm-gate on destructive tools would fall back to its "re-call with confirm=true" mode instead of ever prompting a human. A local model asked "can you delete the roommate group?" as a joke, or a prompt-injection payload hidden in an incoming message, should not be able to act on that.

The fix is to register this server twice under two different names — once with the full tool list for channels you trust, and once scoped down to read-only tools plus create_expense for everything else — and wire only the second registration into the untrusted channel's agent profile.

Hermes Agent (~/.hermes/config.yaml) supports this natively via each server's tools.include/tools.exclude block:

mcp_servers:
  splitwise:
    command: "uvx"
    args: ["--from", "splitwise-mcp-server", "splitwise-mcp"]
    env:
      SPLITWISE_API_KEY: "${SPLITWISE_API_KEY}"
    # Full tool list. Wire this into channels you fully control (CLI, your own account).

  splitwise_readonly:
    command: "uvx"
    args: ["--from", "splitwise-mcp-server", "splitwise-mcp"]
    env:
      SPLITWISE_API_KEY: "${SPLITWISE_API_KEY}"
    tools:
      include:
        - whoami
        - list_groups
        - get_group
        - list_friends
        - list_expenses
        - get_expense
        - create_expense
    # Read-only tools plus create_expense only. Wire this into the WhatsApp channel
    # (or any other agent profile handling messages from people you don't fully trust).

With include set, Hermes never even shows the model the excluded tools exist, so there is no delete_group, delete_expense, or manage_group_members for a WhatsApp message to reach — the worst an untrusted sender can do is add a visible, additive expense (itself still guarded by create_expense's duplicate-detection window), never rewrite or delete shared history.

Gemini CLI offers the same mechanism per server, with includeTools/excludeTools directly in settings.json:

{
  "mcpServers": {
    "splitwise": {
      "command": "uvx",
      "args": ["--from", "splitwise-mcp-server", "splitwise-mcp"],
      "env": { "SPLITWISE_API_KEY": "your-splitwise-api-key" }
    },
    "splitwise_readonly": {
      "command": "uvx",
      "args": ["--from", "splitwise-mcp-server", "splitwise-mcp"],
      "env": { "SPLITWISE_API_KEY": "your-splitwise-api-key" },
      "includeTools": [
        "whoami", "list_groups", "get_group", "list_friends",
        "list_expenses", "get_expense", "create_expense"
      ]
    }
  }
}

Claude Code has no per-server include/exclude field in .mcp.json itself; register the server twice there as shown above, then scope the low-trust name in .claude/settings.json with an explicit allow-list keyed to the mcp__<server>__<tool> pattern. Unlike Gemini CLI's or Hermes's include-lists, this doesn't hide the other tools from the model — it gates the call itself, falling back to Claude Code's normal permission prompt for anything not listed — so for a fully unattended bridge, pair it with whatever default-deny/headless permission mode Claude Code offers rather than relying on the allow rule alone:

{
  "permissions": {
    "allow": [
      "mcp__splitwise_readonly__whoami",
      "mcp__splitwise_readonly__list_groups",
      "mcp__splitwise_readonly__get_group",
      "mcp__splitwise_readonly__list_friends",
      "mcp__splitwise_readonly__list_expenses",
      "mcp__splitwise_readonly__get_expense",
      "mcp__splitwise_readonly__create_expense"
    ]
  }
}

Tool reference

Tool Description Kind
whoami Return the profile of the authenticated Splitwise account. Read-only
list_groups List every Splitwise group the authenticated user belongs to. Read-only
get_group Fetch a single group's detail, including its current members. Read-only
list_friends List every Splitwise friend, with current balances. Read-only
list_expenses List expenses, optionally scoped to one group or friend, paginated. Read-only
get_expense Fetch one expense's full detail, including splits and its comment thread. Read-only
create_expense Create a new expense, split among a group or with a friend. Runs duplicate detection first. Additive
settle_up Record a payment from one user to another, settling part of their balance. Additive
add_comment Post a comment on an expense. Additive
create_group Create a new group and optionally add its initial members. Additive
undelete_expense Restore a previously deleted expense. Additive
undelete_group Restore a previously deleted group. Additive
manage_group_members Add a member (immediate) or remove one (confirm-gated). Additive / Destructive
update_expense Change an expense's description, amount, split, date, or category. Destructive (confirm-gated)
delete_expense Permanently delete an expense. Destructive (confirm-gated)
delete_comment Permanently delete a comment. Destructive (confirm-gated)
delete_group Permanently delete a group. Destructive (confirm-gated)

Two read-only resources are also exposed for reference data: splitwise://categories (every expense category, for create_expense's category_id) and splitwise://currencies (every accepted currency_code).

Safety model

Destructive actions are confirm-gated. update_expense, delete_expense, delete_comment, delete_group, and manage_group_members (when removing a member) all take a confirm: bool = False parameter. With confirm left False, the tool fetches the current state from Splitwise, builds an exact preview of what will change, and calls the MCP client's elicitation capability to ask for interactive yes/no confirmation. If the connected client doesn't support elicitation at all, the tool never performs the write — instead it raises an error containing the full preview and the instruction to re-call with confirm=true. Passing confirm=true skips straight to the write. Additive actions (create_expense, add_comment, settle_up, create_group, undelete_expense, undelete_group, and adding a group member) never require confirmation.

create_expense guards against duplicate writes with a reserve-then-finalize log. A local SQLite log at ~/.splitwise-mcp/write_log.db (directory mode 0700) reserves a row for every create_expense call before it talks to Splitwise, and only marks that row completed once the API call actually succeeds. A repeated call for the same group/friend, amount, and description is handled one of three ways: if an earlier call for the same content is still in flight (reserved but not yet completed, within a 60-second guard window), the retry is rejected outright rather than allowed to race the original request; if a matching call completed in the last 120 seconds, it's rejected with an error describing the existing expense unless allow_duplicate=true is passed; otherwise the write proceeds. An optional idempotency_key lets a caller safely retry the same logical write — a repeated key replays the original result instead of creating a second expense. This narrows, but cannot fully eliminate, the failure window inherent to any client retrying a call whose response was lost: a retry issued after the 60-second guard window has elapsed, with no idempotency_key and against an original request that is still in flight at Splitwise, can still produce a real duplicate.

Money is exact. Every amount is a Python Decimal, never a float. Splitting an expense uses the largest-remainder method: each participant's ideal share is computed as an exact fraction of the total in cents, floored, and any leftover cents are handed out one at a time to the participants with the largest fractional remainder (ties broken by input order) until the split sums to the total exactly, in every mode (equal, exact, percent, shares). exact and percent splits that don't reconcile to the expense total are rejected with the precise shortfall or excess, not silently rounded.

Ambiguous names are never guessed. Friend, group, and category lookups accept either a numeric id or a case-insensitive substring of a name. Zero matches produce an error listing close-but-imperfect suggestions; more than one match produces an error listing every candidate by id and full name so the caller can retry with an exact id. This server never silently picks the "best" match.

Limitations

  • Two-decimal currencies only. All money math assumes currencies with exactly two decimal places (USD, EUR, GBP, and most others). Zero-decimal currencies such as JPY are not handled correctly in this v1 and should not be used with create_expense or update_expense.
  • settle_up is a synthetic payment. Splitwise's API doesn't expose the flag that marks an expense as a true "payment" through this endpoint, so a settlement is recorded as an ordinary expense where the payer owes nothing and the recipient owes the full amount. The balance effect is identical to a real payment, but it will display with a regular expense icon in the Splitwise UI rather than a payment icon.
  • Elicitation support varies by host. The confirm-gate's interactive prompt only works if the connected MCP client implements elicitation. Many hosts, especially chat-bridge and headless integrations, don't — those fall back to the confirm=true re-call pattern for every destructive tool.
  • Duplicate detection is local and per-installation. The write log lives on the machine running the server. It won't catch a duplicate created by a different instance of this server, or one entered directly through the Splitwise app or website.
  • Pagination has no total count. list_expenses's has_more flag is a lower bound (it is true whenever a page came back full), not a guarantee, because the Splitwise API doesn't report how many expenses exist in total.
  • One currency per expense. Splits within a single expense share one currency_code, matching Splitwise's own data model; there is no cross-currency split support.

Development

git clone https://github.com/sarathfrancis90/splitwise-mcp
cd splitwise-mcp
uv venv
source .venv/bin/activate
uv pip install -e '.[dev]'

pytest
ruff check src/ tests/
mypy src/

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

splitwise_mcp_server-0.1.0.tar.gz (60.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

splitwise_mcp_server-0.1.0-py3-none-any.whl (47.8 kB view details)

Uploaded Python 3

File details

Details for the file splitwise_mcp_server-0.1.0.tar.gz.

File metadata

  • Download URL: splitwise_mcp_server-0.1.0.tar.gz
  • Upload date:
  • Size: 60.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for splitwise_mcp_server-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e3b91d169437e8de735846b756f7d295da56b6765b0938e29b7cfb0993d73f4e
MD5 5fcb99591e80cc01d090740eeaa3dddf
BLAKE2b-256 47a2d96ba18c1f6a265c9d398e2103ba36cccf8adc18cd639b3f266c6bd4f92e

See more details on using hashes here.

File details

Details for the file splitwise_mcp_server-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: splitwise_mcp_server-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 47.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for splitwise_mcp_server-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7fbdfb78478981f15edc294588cefc0fdb9a6cbb7317d8883e39cd2bcc98bad4
MD5 0ffcd1da0d86460c971f6406998cd4df
BLAKE2b-256 1c11f856150c6ce702f5fda4a36fe83ce62816bc6466bd95ff58f650a882e71c

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page