Repository navigation
MCP server
Expensave includes an MCP (Model Context Protocol) server, so AI assistants and MCP-capable clients (Claude, Cursor, pi, and others) can manage your expenses programmatically — list and search expenses, add, update or delete them, and pull reports — using the same rules and permissions as the web app.
The endpoint is available at https://your-expensave-host/_mcp (streamable HTTP transport).
The MCP endpoint uses the same JWT authentication as the REST API. Log in to get a token:
curl -X POST https://your-expensave-host/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "password": "your-password"}'The response contains a token (and a refresh_token). Tokens expire — refresh them via
/api/auth/refresh-token when your client stops authenticating.
Add Expensave to your client's MCP configuration (Claude Desktop / Cursor style):
{
"mcpServers": {
"expensave": {
"url": "https://your-expensave-host/_mcp",
"headers": {
"Authorization": "Bearer <token from step 1>"
}
}
}
}For clients with a UI, add a new "remote / HTTP" MCP server with the URL above and an
Authorization: Bearer <token> header.
Ask your assistant something like:
- "Ping my Expensave MCP server"
- "List my Expensave calendars"
- "Add a 12.50 grocery expense for today"
If the assistant can call the tools, you're set.
| Tool | What it does |
|---|---|
ping |
Connectivity check |
list-calendars |
List your calendars (own + shared) |
get-calendar |
Calendar details, including collaborators |
list-categories |
List expense categories |
get-category |
Category details |
list-expenses |
Expenses of a calendar within a date range |
get-expense |
Single expense by ID |
add-expense |
Add an expense (calendar, label, amount, date, optional category/description) |
update-expense |
Update an expense; omitted fields keep current values |
delete-expense |
Delete an expense |
daily-expense-report |
Daily income/expense balances with running balance |
monthly-expense-report |
Same, grouped by month |
category-expenses-report |
Income/expense totals grouped by category |
Notes:
- Amounts follow the app convention: negative = expense, positive = income.
- Dates accept
YYYY-MM-DDorYYYY-MM-DD HH:MM:SS. In reports,1970-01-01as the start date means "from the first expense". - Permissions match the app: you only see your own and shared calendars; you can add expenses to shared calendars but can only edit or delete your own expenses.
- Invalid input (e.g. zero amount, blank label) is rejected with a readable error the assistant can act on.
The endpoint only answers requests whose Host header matches the allowlist. If you expose Expensave
on a public domain, set it in your .env / container environment:
MCP_ALLOWED_HOSTS=expensave.example.comWithout this, clients get 403 Forbidden: Invalid Host header. Multiple hosts are comma-separated
(without ports), e.g. MCP_ALLOWED_HOSTS=expensave.example.com,localhost.
If Expensave sits behind a reverse proxy (Caddy, nginx, Traefik — see Reverse proxy and HTTPS), make sure the proxy:
- forwards the original
Hostheader, - passes the
Authorizationheader through untouched, - forwards
X-Forwarded-Protowhen it terminates TLS.
If the proxy changes the apparent client address and URLs come out wrong, tell Symfony to trust your proxy subnets:
SYMFONY_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16The MCP endpoint is stateless — no sticky sessions needed.
-
401 Unauthorized — token missing, expired, or malformed. Get a fresh token (Step 1) and check the
Authorization: Bearer ...header. -
403 Invalid Host header — add your public hostname to
MCP_ALLOWED_HOSTS(see above). - Tool call returns "Access ... denied" — the account you logged in with has no access to that calendar/expense. Use the web app to check sharing.
-
Connection works locally but not through the proxy — verify the proxy forwards
HostandAuthorizationheaders (see above).
OAuth 2.1 sign-in (so clients can connect without a manually copied token) is planned for a future release.