Skip to content

MCP server

algirdasc edited this page Oct 18, 2026 · 1 revision

MCP server (AI assistants)

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).


Step 1 — Get an access token

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.

Step 2 — Configure your MCP client

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.

Step 3 — Verify

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.


Available tools

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-DD or YYYY-MM-DD HH:MM:SS. In reports, 1970-01-01 as 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.

Server configuration

Allowed hosts (DNS rebinding protection)

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.com

Without this, clients get 403 Forbidden: Invalid Host header. Multiple hosts are comma-separated (without ports), e.g. MCP_ALLOWED_HOSTS=expensave.example.com,localhost.

Behind a reverse proxy

If Expensave sits behind a reverse proxy (Caddy, nginx, Traefik — see Reverse proxy and HTTPS), make sure the proxy:

  • forwards the original Host header,
  • passes the Authorization header through untouched,
  • forwards X-Forwarded-Proto when 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/16

The MCP endpoint is stateless — no sticky sessions needed.


Troubleshooting

  • 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 Host and Authorization headers (see above).

OAuth 2.1 sign-in (so clients can connect without a manually copied token) is planned for a future release.

Clone this wiki locally