bokio-mcp
# bokio-mcp
An [MCP](https://modelcontextprotocol.io) server for the [Bokio](https://www.bokio.se)
accounting API. It exposes **85 tools** over one Bokio company — invoices, customers,
suppliers, supplier invoices, journal entries, credit notes, the chart of accounts,
fiscal years, items, tags, uploads, SIE export and bank payments — so an MCP client can
read and (optionally) write your bookkeeping.
No accounts, no database, no hosted service. One company per server instance.
```
"Which invoices are still unpaid?"
"Book the attached receipt against account 6570."
"Export the SIE file for the 2025 fiscal year."
```
## Quick start
Try it with fixtures, no Bokio account needed:
```bash
BOKIO_MOCK=true npx bokio-mcp status
# { "companyName": "Testbolaget AB", "status": "active", ... }
```
`BOKIO_MOCK=true` serves an in-process fake Bokio, so the full tool surface works
before you have any credentials.
For real data, pick one of the two credential routes below and add the server to your
MCP client.
### Route A — private integration token (recommended)
In Bokio: **company settings → Integrations → API tokens → Create private integration**.
The token is long-lived, scoped to that one company, and needs no OAuth review.
```json
{
"mcpServers": {
"bokio": {
"command": "npx",
"args": ["-y", "bokio-mcp"],
"env": {
"BOKIO_INTEGRATION_TOKEN": "your-token",
"BOKIO_COMPANY_ID": "the-company-uuid-from-the-bokio-url"
}
}
}
}
```
### Route B — OAuth
Register an integration in Bokio's developer portal with the redirect URI
`http://127.0.0.1:7337/callback`, then:
```bash
export BOKIO_CLIENT_ID=... BOKIO_CLIENT_SECRET=...
npx bokio-mcp login
```
That opens Bokio in your browser and writes the tokens to
`~/.config/bokio-mcp/tokens.json` (mode `0600`). Access tokens are refreshed
automatically, including refresh-token rotation. Point your MCP client at
`npx -y bokio-mcp` with the same two env vars set.
## Writes are off by default
A fresh install can read but not write. Mutating tools are not merely blocked — they are
**absent from `tools/list`**, so the model never proposes an edit it cannot make:
```bash
BOKIO_ALLOW_WRITES=true
```
Of the 85 tools, 38 are read-only and 47 mutate. Start without the flag, confirm the
model is reading what you expect, then turn writes on.
## Commands
| | |
|---|---|
| `bokio-mcp` | Serve over stdio — what MCP clients launch |
| `bokio-mcp serve --http` | Serve over streamable HTTP on `$PORT` |
| `bokio-mcp login` | Connect a company via OAuth |
| `bokio-mcp status` | Print the current connection and exit |
## HTTP transport
For self-hosting, e.g. behind a reverse proxy or in a container:
```bash
MCP_AUTH_TOKEN=$(openssl rand -hex 32) bokio-mcp serve --http
```
`/mcp` is then guarded by that static bearer. **Without `MCP_AUTH_TOKEN` the server
binds `127.0.0.1` only** rather than exposing an unauthenticated bridge to live
accounting data. A `Dockerfile` is included; it defaults to this transport.
## Environment
| Variable | Default | |
|---|---|---|
| `BOKIO_INTEGRATION_TOKEN` | — | Route A: private integration token |
| `BOKIO_COMPANY_ID` | — | Route A: the company's UUID |
| `BOKIO_CLIENT_ID` / `BOKIO_CLIENT_SECRET` | — | Route B: OAuth credentials |
| `BOKIO_ALLOW_WRITES` | `false` | Register mutating tools |
| `BOKIO_MOCK` | `false` | Serve in-process fixtures instead of Bokio |
| `BOKIO_SCOPES` | see `src/config.ts` | OAuth scopes requested by `login` |
| `BOKIO_TOKEN_FILE` | `~/.config/bokio-mcp/tokens.json` | Where OAuth tokens live |
| `BOKIO_TOKEN_ENCRYPTION_KEY` | — | Optional AES-256-GCM at rest; `openssl rand -base64 32` |
| `BINARY_MAX_BYTES` | `4194304` | Cap on inline uploads/downloads |
| `PORT` / `HOST` | `3000` / auto | HTTP transport |
| `MCP_AUTH_TOKEN` | — | Bearer guarding `/mcp` |
| `OAUTH_CALLBACK_PORT` | `7337` | Loopback port used by `login` |
See `.env.example` for the annotated version.
### A note on token storage
The OAuth token file is plaintext at mode `0600` unless `BOKIO_TOKEN_ENCRYPTION_KEY` is
set — the same posture as the `gh` and `aws` CLIs. On a personal machine a key stored
next to the thing it encrypts adds little; set it when the file lives somewhere less
private, such as a container image or a shared host.
## Development
```bash
npm install
npm test # 36 tests, no database, no credentials
npm run typecheck
npm run dev # tsx watch, stdio transport
```
`BOKIO_MOCK=true` routes every Bokio request into an in-process mock at the *fetch*
layer, so it behaves identically under stdio, under HTTP, and inside tests with no
server running. `test/mcp.test.ts` drives the real MCP protocol end to end against it.
Tool definitions are hand-written in `src/tools/bokio/`, but each one's path and method
are checked against the OpenAPI spec at compile time by the `op()` helper — a typo in a
route fails the build. Regenerate the spec types with:
```bash
npm run gen:bokio
```
## Not supported
- **More than one company per instance.** Bokio credentials are per-company; run a
second instance for a second company.
- **Elevated scopes.** `bank-payments:*` requires per-integration approval from Bokio.
The bank payment tools are registered but will fail until your integration is
approved and the scopes are added to `BOKIO_SCOPES`.
## License
MIT
TDQS
Scored across 40 tools
Each tool targets a distinct entity or action (list vs get vs download) with clear hierarchical relationships (e.g., invoice → payments/settlements/attachments). No overlapping purposes that would cause misselection.
All domain tools follow a strict 'bokio_verb_noun' pattern (list/get/download), with utility tools (ping, get_connection_status) clearly separated. The naming is highly predictable and consistent.
With 40 tools, the count is on the heavier side but justified by the breadth of accounting entities (invoices, customers, journal entries, items, uploads, etc.). Each tool serves a specific resource, though some consolidation could reduce redundancy.
The tool surface comprehensively covers read operations across all major accounting entities, including downloads and SIE export. However, it lacks any create/update/delete tools, so the lifecycle is incomplete unless the server is intentionally read-only.