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.
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:
- Go to secure.splitwise.com/apps and register an application (any name and description work).
- Copy the generated API key.
- Set it as
SPLITWISE_API_KEYwherever 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:
- Register an application at the same secure.splitwise.com/apps page and note its consumer key and secret.
- Set that application's Redirect URI / Callback URL to
http://127.0.0.1:61438/callback.splitwise-mcp-authalways 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. - Set
SPLITWISE_CONSUMER_KEYandSPLITWISE_CONSUMER_SECRETin the environment. - Run
splitwise-mcp-authonce per account. It opens a browser to Splitwise's authorization page, receives the redirect on a local server listening at127.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_expenseorupdate_expense. settle_upis 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=truere-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'shas_moreflag 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3b91d169437e8de735846b756f7d295da56b6765b0938e29b7cfb0993d73f4e
|
|
| MD5 |
5fcb99591e80cc01d090740eeaa3dddf
|
|
| BLAKE2b-256 |
47a2d96ba18c1f6a265c9d398e2103ba36cccf8adc18cd639b3f266c6bd4f92e
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7fbdfb78478981f15edc294588cefc0fdb9a6cbb7317d8883e39cd2bcc98bad4
|
|
| MD5 |
0ffcd1da0d86460c971f6406998cd4df
|
|
| BLAKE2b-256 |
1c11f856150c6ce702f5fda4a36fe83ce62816bc6466bd95ff58f650a882e71c
|