open-splitwise
Enables AI agents to manage Splitwise expenses through natural language, including reading balances, splitting costs, and handling authentication and rate limits automatically.
README
<div align="center">
open-splitwise
Turn Splitwise into an agent-native expense tracker.
An open Model Context Protocol (MCP) server that lets any AI agent — Hermes, Claude Desktop, Claude Code, Cursor, or anything that speaks MCP — read balances, split expenses from messy natural language, diagnose its own auth problems, and never think about rate limits.
Python 3.11+ · MCP spec 2026-07-28 · stdio transport · 33 tools · lazy-loaded
</div>
Why
Existing Splitwise integrations hand the model a raw API mirror and hope for the best.
That fails in predictable ways: the model invents category IDs, mis-splits ₹300 three ways,
believes Splitwise's 200 OK when the request actually failed, or treats a rate-limit
response as a bug to retry aggressively.
open-splitwise fixes this at the server layer:
| Problem for agents | What open-splitwise does |
|---|---|
| "Split dinner with Alice" requires 3–4 API calls + arithmetic | quick_add_expense resolves names → IDs, computes cent-exact shares, picks the category, posts once |
| Two Alices in your friends list | resolve_users returns candidate lists so the agent asks you which one |
| "What do I owe?" needs multi-endpoint aggregation | money_summary returns per-currency totals in one call |
Splitwise returns 200 OK with an errors object |
Server checks it; failures surface as tool errors with actionable text — never false success |
| HTTP 429 rate limits | Retried invisibly (Retry-After honored, exponential backoff fallback) |
| Key revoked / logged out mid-session | Errors tell the agent the cause and to run setup_auth; new keys apply instantly, no restart |
| 33 tool schemas burn ~4k tokens in every prompt | Lazy tool discovery: only 7 essential tools are exposed by default; search_tools("expenses") loads the rest on demand with full schemas |
Features
- Complete API coverage — all 27 endpoints of the official Splitwise OpenAPI 3.0 spec, one tool each, faithful names.
- Workflow layer — high-level tools so a single utterance maps to a single call.
- Self-service auth lifecycle —
setup_authvalidates a key live against Splitwise before storing it (wrong keys are never persisted),get_auth_statusexplains what's configured,logoutclears credentials. Re-auth works mid-session. - Honest errors — every failure mode (unresolved person, share-sum mismatch, unknown category, revoked key, exhausted retries) returns text telling the agent exactly what happened and what to do next.
- Safe-by-default annotations — reads carry
readOnlyHint, destructive deletes carrydestructiveHint, per MCP 2026-07-28 semantics. Tools register in deterministic order for cache-friendly discovery. - Local-first secrets — API key stored at
~/.config/splitwise-mcp/credentials.json, mode0600, atomic writes, never echoed back (masked previews only).
Quick start
git clone https://github.com/<you>/open-splitwise.git
cd open-splitwise
uv sync
Run it standalone (stdio):
uv run open-splitwise # starts with no key configured — see auth below
Get an API key at https://secure.splitwise.com/apps (Account Settings → API keys).
Connect any MCP client
Generic stdio block (Claude Desktop claude_desktop_config.json, Claude Code .mcp.json,
Cursor, …):
{
"mcpServers": {
"splitwise": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/open-splitwise", "run", "open-splitwise"],
"env": { "SPLITWISE_API_KEY": "<optional: preconfigure>" }
}
}
}
Connect Hermes Agent
Add to ~/.hermes/config.yaml:
mcp_servers:
splitwise:
command: "uv"
args: ["--directory", "/absolute/path/to/open-splitwise", "run", "open-splitwise"]
env:
SPLITWISE_API_KEY: "<optional>"
tools:
include: [quick_add_expense, resolve_users, money_summary, get_auth_status]
prompts: false
resources: false
Then /reload-mcp. Start with the four workflow/auth tools above; add raw API tools only
when needed — Hermes' per-server filtering keeps the tool surface small.
Authentication lifecycle
The server is designed so agents diagnose and fix auth themselves, asking you only for the secret:
| Situation | Agent-visible behavior |
|---|---|
| No key anywhere | Every tool fails with: "No Splitwise API key is configured. Ask the user to generate one at secure.splitwise.com/apps, then call setup_auth." |
| User provides a key | setup_auth(api_key) probes /get_current_user first — invalid keys are rejected, not stored; valid keys are saved and who they belong to is reported |
| Key revoked / account logged out (HTTP 401/403) | Tools fail with "key may have been revoked, expired, or the account was logged out… ask the user for a fresh key and call setup_auth" |
| Diagnosis | get_auth_status() → {configured, source: stored|environment, masked_key} |
| Switching accounts | logout() deletes the stored credential |
Key resolution happens per request: stored credential → SPLITWISE_API_KEY env var →
none. A freshly saved key takes effect immediately in the running process — zero restarts.
Credentials live at ~/.config/splitwise-mcp/credentials.json (mode 0600). Override the
directory with SPLITWISE_MCP_CONFIG_DIR (handy for tests or multi-profile setups).
Agent ergonomics
You: "add dinner 900 split with alice and bob@x.com, groceries"
Agent: quick_add_expense(description="Dinner", cost="900.00",
participants=["alice", "bob@x.com"],
category_name="groceries")
Server: resolves alice→12? two matches! → error listing Alice A (id 10), Alice Wood (id 12)
Agent: "Which Alice?" → you answer → re-call succeeds
Server: { status: created, expense_id: 99123,
splits: [ "Nikhil paid 900.00 INR",
"Alice A owes 300.00 INR",
"Bob B owes 300.00 INR" ] }
quick_add_expense— names/partial-names/emails/IDs accepted; equal shares computed with remainder cents distributed deterministically; customowed_sharesvalidated to sum exactly; payer included by default (include_payer_in_split=falsewhen they didn't consume); currency defaults from your profile.resolve_users— email exact-match, full-name match, unique first-name, substring fallback; ambiguity returns candidates instead of guessing.money_summary— per-currencyowed_to_you/you_owe/net, friend-level balances, and group simplified debts involving you.
Tool reference (33)
| Group | Tools |
|---|---|
| Workflows | quick_add_expense · resolve_users · money_summary |
| Users | get_current_user · get_user · update_user |
| Groups | get_groups · get_group · create_group · delete_group* · undelete_group · add_user_to_group · remove_user_from_group |
| Friends | get_friends · get_friend · create_friend · create_friends · delete_friend* |
| Expenses | get_expenses · get_expense · create_expense · update_expense · delete_expense* · undelete_expense |
| Comments | get_comments · create_comment · delete_comment* |
| Notifications | get_notifications |
| Other | get_currencies · get_categories |
| Auth | setup_auth · get_auth_status · logout* |
* annotated destructiveHint=true; all get_* tools annotated readOnlyHint=true.
Prefer workflow tools over their raw counterparts whenever both exist.
Rate limiting
Splitwise answers HTTP 429 when throttled. open-splitwise retries automatically:
Retry-After header honored verbatim; otherwise exponential backoff (0.5 s doubling,
capped at 30 s), up to 3 attempts by default. Agents see an error only if every attempt is
exhausted — and that error says to slow down, not retry blindly.
Configuration
| Env var | Default | Purpose |
|---|---|---|
SPLITWISE_API_KEY |
– | Bootstrap key (stored credentials take precedence) |
SPLITWISE_MCP_CONFIG_DIR |
~/.config/splitwise-mcp |
Where credentials.json lives |
SPLITWISE_MCP_MAX_RETRIES |
3 |
429 retry attempts before surfacing |
SPLITWISE_MCP_LAZY |
on |
off registers all 33 tools upfront |
Splitwise quirks handled for you
- Array params flattened to Splitwise's odd
users__{index}__{property}encoding 200 OK ≠ success:errors{}/success:falsechecked on every mutation- Money as decimal strings with 2 dp; remainder cents distributed, sums always exact
category_idmust be a subcategory — enforced via fuzzy name resolution- Balances/debts read from pre-computed
balance[]/simplified_debts(never recomputed) - "Settle up" is just an expense with
payment:true(no dedicated endpoint exists) - OAuth2 exists but is deliberately out of scope: personal API keys fit the agent-asks-user flow; OAuth needs a redirect URI + browser (hosted deployments only)
Architecture
┌─────────────── any MCP client ───────────────┐
│ Hermes / Claude Desktop / Cursor / … │
└──────────────────┬───────────────────────────┘
│ JSON-RPC over stdio
┌──────────────────▼───────────────────────────┐
│ server.py — FastMCP app, 33 tools │
│ workflows · raw endpoints · auth lifecycle │
├──────────────────────────────────────────────┤
│ client.py — async REST client │
│ bearer auth (per-request key resolution) │
│ param flattening · success verification │
│ transparent 429 retry/backoff │
├──────────────────────────────────────────────┤
│ auth.py — credentials.json (0600, atomic) │
└──────────────────┬───────────────────────────┘
│ HTTPS
secure.splitwise.com/api/v3.0
Development
uv run pytest # 54 tests: client, rate limits, auth, workflows, lazy loading, MCP semantics
uv run python scripts/smoke_stdio.py # real subprocess: handshake, discovery, live auth-failure paths
Built test-first (strict TDD): every behavior above has a failing-test-first provenance. Layout:
src/open_splitwise/
client.py # REST client: auth provider, flattening, retry, error mapping
auth.py # credential storage
server.py # FastMCP definitions: workflows + raw + auth tools
tests/
scripts/smoke_stdio.py
Terms of use
Splitwise's self-serve API is non-commercial per their API terms. Your API key grants full access to your account — treat it like a password. This project is an independent integration and is not affiliated with or endorsed by Splitwise Inc.
Roadmap
- [ ] Receipt upload on expense creation
- [ ] Multi-currency expense helper with conversion awareness
- [ ] Recurring-expense summaries as an MCP prompt
- [ ] Optional Streamable HTTP transport for hosted/multi-user deployments (+OAuth2)
- [ ] Publish to PyPI (
uvx open-splitwise)
Contributing
PRs welcome — please keep the TDD discipline (tests fail first, then pass), keep tool descriptions written for models, and never log secrets.
License
MIT — open for everyone: use it, modify it, ship it, sell with it. Just keep the copyright notice.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
E2B
Using MCP to run code via e2b.