Configuration
crunch-mcp is configured entirely through environment variables. With Docker Compose, put them in .env. Copy example.env to get started.
The server checks the configuration at startup. If something is wrong, it exits with a clear message such as CRUNCH_ALLOW_DELETES=true also requires CRUNCH_ALLOW_WRITES=true. If the container keeps restarting, check docker compose logs. The server starts without a login, though: tools then explain how to log in.
Crunch API and login
Section titled “Crunch API and login”| Variable | Default | Description |
|---|---|---|
CRUNCH_CLIENT_ID |
required to log in | The client ID Crunch gave you when it approved your API application. |
CRUNCH_CLIENT_SECRET |
Only for confidential clients, which Crunch issues a secret. Leave it empty for public clients. With a secret, crunch-mcp authenticates to Crunch’s token endpoint with HTTP Basic. Either way it uses PKCE. | |
CRUNCH_REDIRECT_URI |
http://127.0.0.1:8976/callback |
Where Crunch sends the browser after you click Allow. If your API account has a redirect URI registered with Crunch, this must match it exactly. A loopback address must use http://. |
CRUNCH_SCOPES |
follows the settings below | Space-separated OAuth scopes to ask for at login. The default is read:all, plus create:all update:all with writes, delete:all with deletes, and offline_access for a refresh token. Set it to, for example, all offline_access to ask for everything. |
CRUNCH_TOKEN_FILE |
~/.config/crunch-mcp/tokens.json (/data/tokens.json in the image) |
Where tokens are saved. It must be writable and must survive restarts. In Docker, keep a volume mounted at /data. If XDG_CONFIG_HOME is set, the default is $XDG_CONFIG_HOME/crunch-mcp/tokens.json. |
CRUNCH_REFRESH_TOKEN |
A refresh token to start from, instead of logging in. It’s saved to the token file and replaced as it rotates. See Starting from a refresh token. | |
CRUNCH_ACCESS_TOKEN |
A fixed access token, used until Crunch rejects it. Handy for a quick test. It lasts about an hour, and after that the server needs a refresh token or a login. | |
CRUNCH_LOGIN_LISTEN |
the redirect URI’s host and port (0.0.0.0:8976 in the image) |
The address crunch-mcp login listens on to catch the redirect. The image sets 0.0.0.0:8976 so the published port reaches the container. |
CRUNCH_API_URL |
https://public-api.crunch.co.uk/v1 |
The Crunch public API. You shouldn’t need to change it. |
CRUNCH_AUTH_URL |
https://oauth.crunch.co.uk |
The Crunch OAuth server (with /authorize and /token). You shouldn’t need to change it. |
CRUNCH_TIMEOUT_MS |
30000 |
Timeout for each request to Crunch, in milliseconds. |
How login works
Section titled “How login works”Crunch’s public API uses the OAuth 2.0 authorisation code flow. In short:
- crunch-mcp builds a URL on Crunch’s OAuth server with your client ID, the scopes it wants and a PKCE challenge.
- You open it in a browser, log in to Crunch and click Allow.
- Crunch sends the browser to the redirect URI with a one-time code.
- crunch-mcp swaps the code for an access token (valid for about an hour) and a refresh token (valid for 60 days), and saves them to the token file.
- From then on, it uses the access token for API calls and quietly gets a new one with the refresh token when it runs out.
You do steps 2 and 3. There are three ways to start the flow and hand back the code:
| Method | How | Good for |
|---|---|---|
crunch-mcp login on your computer |
Run the binary in a terminal. It listens on 127.0.0.1:8976, so the browser’s redirect comes straight back to it. |
Running the binary locally, or with stdio clients. |
crunch-mcp login in Docker |
docker compose run --rm -p 8976:8976 crunch-mcp login. The container listens on port 8976. If your browser is on another machine, the redirect page fails to load. Copy the URL from the address bar and paste it into the terminal. |
The compose setup. |
| From the assistant | Ask it to “log in to Crunch”. It calls crunch_auth_start, which gives you the URL. After Allow, the redirect page fails to load (the running server doesn’t listen on 8976). Paste the URL from the address bar into the chat, and it calls crunch_auth_complete. |
Logging in, or switching account, without a terminal. The new tokens are used straight away. |
The login command waits up to 15 minutes, and a login started with crunch_auth_start must be finished within 15 minutes by the same running server.
You don’t need to register the redirect URI with Crunch unless your API account already has one set. In that case, CRUNCH_REDIRECT_URI must match it exactly, or Crunch rejects the login with an invalid_redirect_uri error. If it isn’t a loopback address, crunch-mcp login doesn’t listen for it, and you paste the URL instead.
Scopes
Section titled “Scopes”Crunch’s scopes are either all, or one per kind of operation: read:all, create:all, update:all and delete:all. offline_access adds a refresh token, so the server stays logged in for more than an hour.
crunch-mcp asks only for what your settings need:
| Settings | Scopes requested |
|---|---|
| Read-only (default) | read:all offline_access |
CRUNCH_ALLOW_WRITES=true |
read:all create:all update:all offline_access |
+ CRUNCH_ALLOW_DELETES=true |
read:all create:all update:all delete:all offline_access |
Scopes are fixed when you log in. After you turn on writes or deletes, log in again so the new login includes them. crunch_auth_status and crunch-mcp status show the scopes the current login has.
Refresh tokens rotate
Section titled “Refresh tokens rotate”For API applications created since 14 January 2026, Crunch rotates refresh tokens. Every refresh returns a new refresh token, and if an old one is ever used again, Crunch treats it as stolen and revokes the whole login. crunch-mcp is built around this:
- It writes the new refresh token to the token file before it uses the new access token, so a crash or restart never loses it. The file is replaced atomically and is readable only by its owner (mode
0600). - It never runs two refreshes at once. Requests that arrive together wait for one refresh and share its result.
- If it can’t save the file (for example, a read-only filesystem without a volume), it keeps working from memory and warns in the log and in
crunch_auth_status. A restart would then need a new login.
That leads to two rules:
- The token file must persist. In Docker, keep a volume at
/data. - Only one server per login. Two servers, or two stdio sessions, that share a token file or were started from the same refresh token will eventually reuse an old refresh token and get logged out. If you need several servers, log each one in separately with its own token file.
A refresh token lasts 60 days. If the server isn’t used for that long, or Crunch revokes the login, tools say so and you log in again.
Starting from a refresh token
Section titled “Starting from a refresh token”If you already have a refresh token, for example from another tool, set CRUNCH_REFRESH_TOKEN instead of logging in. The server saves it to the token file on first use and follows its rotations from there. The variable can stay set: the token file remembers which CRUNCH_REFRESH_TOKEN it descends from, so a stale value doesn’t override newer tokens. If you put a different refresh token in the variable, it takes over from what’s in the file.
Don’t use the same refresh token anywhere else afterwards. Its first use by crunch-mcp makes it stale.
What the assistant may do
Section titled “What the assistant may do”| Variable | Default | Description |
|---|---|---|
CRUNCH_ALLOW_WRITES |
false |
Register tools that create or change records: expenses, payments, attachments, invoices (including issuing them), clients, suppliers, recurring expenses, bank accounts and credit cards. |
CRUNCH_ALLOW_DELETES |
false |
Also register tools that delete records or remove payments. Requires CRUNCH_ALLOW_WRITES=true. |
CRUNCH_ALLOW_EMAIL |
false |
Also register crunch_email_invoice, which emails an invoice to a customer from Crunch. Requires CRUNCH_ALLOW_WRITES=true. |
CRUNCH_TOOLSETS |
all |
Comma-separated list of toolsets to expose. |
Boolean variables accept true/false, 1/0, yes/no and on/off. Anything else stops the server with an error.
That gives four access levels. The tool reference labels every tool with the level it needs:
| Label | Registered when |
|---|---|
| 🟢 read | Always |
| 🟠 write | CRUNCH_ALLOW_WRITES=true |
| 🔴 delete | CRUNCH_ALLOW_WRITES=true and CRUNCH_ALLOW_DELETES=true |
CRUNCH_ALLOW_WRITES=true and CRUNCH_ALLOW_EMAIL=true |
Deletes and email are independent of each other: you can allow either without the other.
Tools that aren’t allowed are not registered at all. The assistant can’t see them, so it can’t call them or be talked into calling them. Crunch also checks the login’s scopes on every request, so a write fails with 403 if the login only has read:all.
Turning on writes
Section titled “Turning on writes”-
Add the settings to
.env, for exampleCRUNCH_ALLOW_WRITES=true. -
Log in again so the login includes the extra scopes, and start the server with the new settings:
Terminal window docker compose stopdocker compose run --rm -p 8976:8976 crunch-mcp logindocker compose up -d -
Reconnect or restart your MCP client so it fetches the new tool list.
CRUNCH_ALLOW_EMAIL doesn’t need a new scope of its own, but it does need writes, so the same steps apply if writes were off before.
MCP transport
Section titled “MCP transport”| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT |
http in the Docker image (stdio for the binary) |
http for Streamable HTTP, or stdio. |
MCP_HTTP_HOST |
0.0.0.0 |
Address to listen on (HTTP only). |
MCP_HTTP_PORT |
8080 |
Port to listen on (HTTP only). |
MCP_AUTH_TOKEN |
When set, every request to /mcp must send Authorization: Bearer <token>. Set this whenever anything other than you can reach the port. |
|
MCP_ALLOWED_HOSTS |
Comma-separated allow-list of Host header values, such as crunch-mcp.lan,crunch-mcp.lan:8080. Requests with any other Host are rejected, which protects against DNS-rebinding attacks from web pages in your browser. |
The HTTP server exposes these endpoints:
| Path | Method | Purpose |
|---|---|---|
/mcp |
POST |
The MCP endpoint (stateless Streamable HTTP, JSON responses). Request bodies are limited to 16 MiB, enough for attachments sent as base64. |
/health, /healthz |
GET |
Liveness check. Returns {"status":"ok"} and doesn’t contact Crunch. |
Docker secrets (_FILE variables)
Section titled “Docker secrets (_FILE variables)”Every variable can also be read from a file: set <NAME>_FILE to the file’s path. Leading and trailing whitespace is trimmed. This works with Docker secrets and Kubernetes secret volumes:
services: crunch-mcp: image: ghcr.io/mattoddie/crunch-mcp:latest environment: CRUNCH_CLIENT_ID: your-client-id CRUNCH_CLIENT_SECRET_FILE: /run/secrets/crunch_client_secret MCP_AUTH_TOKEN_FILE: /run/secrets/mcp_auth_token secrets: [crunch_client_secret, mcp_auth_token] volumes: [crunch-data:/data] ports: ["8080:8080"]
secrets: crunch_client_secret: file: ./secrets/crunch_client_secret.txt mcp_auth_token: file: ./secrets/mcp_auth_token.txt
volumes: crunch-data:If both NAME and NAME_FILE are set, the file wins. The tokens themselves don’t belong in a secret: they change all the time, so they live in CRUNCH_TOKEN_FILE on a writable volume.
Choosing toolsets
Section titled “Choosing toolsets”Every tool’s name, description and argument schema is sent to the model, which uses up context. Exposing only what you need keeps the assistant faster and more focused:
| Toolset | Use it for |
|---|---|
account |
Login, company details and the business overview. Always include it. |
reference |
Expense types, VAT types, payment methods, countries, accounting periods, people. Include it whenever you allow writes, so the assistant can look up valid values. |
banking |
Bank accounts and credit cards, balances, recorded payments |
clients |
Customers you invoice |
suppliers |
Who you buy from |
expenses |
Expenses, payments and receipts |
recurring |
Recurring expenses |
invoices |
Sales invoices, payments received, PDFs |
raw |
Any API path. Handy, but gives broad access. |
The tool reference lists the tools in each toolset. A read-only setup registers about half the tools. Some suggested sets:
| Goal | CRUNCH_TOOLSETS |
|---|---|
| Overview and reporting | account,banking,expenses,invoices,clients,suppliers |
| Recording receipts and bills | account,reference,expenses,suppliers,banking |
| Invoicing and credit control | account,reference,invoices,clients,banking |
| Everything (default) | all |
Example configurations
Section titled “Example configurations”Read-only (the safest setup):
CRUNCH_CLIENT_ID=your-client-idMCP_AUTH_TOKEN=...Bookkeeping. The assistant can record expenses, payments and invoices, but can’t delete anything or email customers:
CRUNCH_CLIENT_ID=your-client-idMCP_AUTH_TOKEN=...CRUNCH_ALLOW_WRITES=trueCRUNCH_TOOLSETS=account,reference,banking,clients,suppliers,expenses,invoicesFull control, for a single trusted user:
CRUNCH_CLIENT_ID=your-client-idCRUNCH_CLIENT_SECRET=...MCP_AUTH_TOKEN=...CRUNCH_ALLOW_WRITES=trueCRUNCH_ALLOW_DELETES=trueCRUNCH_ALLOW_EMAIL=trueA quick test with an access token you already have (no refresh, so it stops working after about an hour):
CRUNCH_ACCESS_TOKEN=...MCP_AUTH_TOKEN=...