Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
# jortt-mcp
A [Model Context Protocol ](https://modelcontextprotocol.io ) (MCP) server for the
[Jortt ](https://www.jortt.nl ) accounting & invoicing API.
It exposes the full Jortt REST API — customers, invoices, estimates, expenses,
projects, bank accounts, reports, and more — as MCP tools, so an MCP-capable
assistant (Claude Desktop, Claude Code, etc.) can read and write your Jortt
administration on your behalf.
The tools are generated directly from Jortt's published
[OpenAPI specification ](https://api.jortt.nl/swagger_doc ), so coverage stays
faithful to the API: **82 tools** across every documented resource.
## How it works
2026-07-11 19:10:55 +02:00
- The Jortt OpenAPI spec is committed at `src/openapi.json` and bundled into
`dist/` at build time. On startup the server reads that spec, resolves all
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
schema references, and turns every operation into an MCP tool with a proper
2026-07-11 19:10:55 +02:00
JSON Schema for its arguments. Refresh the spec explicitly with
`npm run fetch-spec` ; the server never loads it from the network unless you
set `JORTT_OPENAPI_URL` deliberately.
- Authentication towards Jortt uses the OAuth 2.0 **client credentials** grant
(the flow Jortt recommends for applications bound to your own
administration). The server fetches a bearer token, caches it until shortly
before it expires, and transparently refreshes on expiry or a `401` .
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
- Path parameters, query parameters, and JSON request bodies are all handled
automatically. Request bodies are passed under a single `body` argument.
2026-07-11 19:10:55 +02:00
- Two transports: **stdio** (default, for local clients like Claude Desktop)
and **Streamable HTTP** (`MCP_TRANSPORT=http` ) for remote access. The HTTP
transport refuses to start without authentication configured.
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
## Prerequisites
1. A [Jortt account ](https://app.jortt.nl/van-start ).
2. A registered API application. Create one under
**Profile → API** →
[OAuth applications ](https://app.jortt.nl/new_profile/api/jortt/oauth_applications/list ),
selecting the scopes you want to grant. Registration gives you a
**Client ID** and **Client secret** .
## Installation
```bash
git clone https://github.com/deprekated/jortt-mcp.git
cd jortt-mcp
2026-07-11 19:10:55 +02:00
npm install
npm run build
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
```
2026-07-11 19:10:55 +02:00
On Nix, `nix build github:deprekated/jortt-mcp` — or see [NixOS ](#nixos ) below
for the module.
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
## Configuration
2026-07-11 19:10:55 +02:00
The server is configured entirely through environment variables. Every
secret-bearing variable can instead be read from a file by appending `_FILE`
(e.g. `JORTT_CLIENT_SECRET_FILE=/run/agenix/jortt-client-secret` ), which keeps
secrets out of process environments and works directly with agenix / systemd
credentials.
### Core
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
| Variable | Required | Description |
| -------------------- | -------- | --------------------------------------------------------------------------- |
| `JORTT_CLIENT_ID` | yes | OAuth client ID of your registered application. |
| `JORTT_CLIENT_SECRET` | yes | OAuth client secret. |
| `JORTT_SCOPES` | no | Space-separated scopes to request. Defaults to all scopes; the token server only grants scopes your application was registered with. |
2026-07-11 19:10:55 +02:00
| `JORTT_READ_ONLY` | no | `1` /`true` : expose only GET (list/get) tools — a hard stop on writes regardless of scopes. |
| `JORTT_OPENAPI_URL` | no | Load the OpenAPI spec from this URL instead of the bundled copy. Off by default. |
### HTTP transport & authentication
| Variable | Description |
| --------------------- | ------------------------------------------------------------------------------ |
| `MCP_TRANSPORT` | `stdio` (default) or `http` (Streamable HTTP). |
| `MCP_HOST` / `MCP_PORT` | Bind address and port. Defaults: `127.0.0.1:3910` . The server speaks plain HTTP — terminate TLS in a reverse proxy. |
| `MCP_AUTH_MODE` | `token` or `oidc` . **Required** in http mode; the server refuses to run unauthenticated. |
| `MCP_AUTH_TOKEN` | Static bearer token(s), comma/newline separated (`token` mode). Generate with `openssl rand -hex 32` . Compared in constant time; minimum 16 chars. |
| `MCP_OIDC_ISSUER` | Issuer URL of your OAuth authorization server / proxy (`oidc` mode). |
| `MCP_RESOURCE_URL` | Public URL of the MCP endpoint (e.g. `https://jortt-mcp.example.com/mcp` ). Used as token audience and published in RFC 9728 protected-resource metadata for MCP client discovery. |
| `MCP_OIDC_AUDIENCE` | Override the audience to require in tokens (default: `MCP_RESOURCE_URL` ). |
| `MCP_OIDC_ALLOWED_SUBJECTS` | Comma-separated allowlist matched against `sub` , `preferred_username` , and `email` . Strongly recommended — without it, *any* account on your IdP is accepted. |
| `MCP_OIDC_INTROSPECTION_URL` , `MCP_OIDC_CLIENT_ID` , `MCP_OIDC_CLIENT_SECRET` | Use RFC 7662 token introspection instead of JWT validation, for issuers with opaque access tokens (e.g. Authelia). The endpoint is discovered from the issuer when the URL is omitted. |
| `MCP_ALLOWED_HOSTS` / `MCP_ALLOWED_ORIGINS` | Exact-match allowlists for the `Host` / `Origin` headers (DNS-rebinding protection). Include the public hostname (with `:port` if non-standard). |
In `oidc` mode the server is a standard OAuth 2.0 **resource server** per the
[MCP authorization spec ](https://modelcontextprotocol.io/specification/draft/basic/authorization ):
it validates bearer tokens issued by your IdP/OAuth proxy (Keycloak, Authelia,
Pomerium, Kanidm, ...) and serves `/.well-known/oauth-protected-resource` so
clients like the claude.ai remote-connector UI can discover the authorization
server and run the authorization-code + PKCE flow against it. For claude.ai,
your IdP needs either dynamic client registration or a manually registered
client whose ID/secret you paste into the connector settings; the redirect URL
to register is `https://claude.ai/api/mcp/auth_callback` .
Endpoints in http mode: `POST/GET/DELETE /mcp` (MCP, authenticated),
`GET /healthz` (unauthenticated liveness probe),
`GET /.well-known/oauth-protected-resource` (only when OIDC is configured).
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
### Claude Desktop / Claude Code
Add the server to your MCP client configuration (for Claude Desktop, this is
`claude_desktop_config.json` ):
```json
{
"mcpServers": {
"jortt": {
"command": "node",
"args": ["/absolute/path/to/jortt-mcp/dist/index.js"],
"env": {
"JORTT_CLIENT_ID": "your-client-id",
"JORTT_CLIENT_SECRET": "your-client-secret"
}
}
}
}
```
Restrict what the assistant can do by narrowing the scopes:
```json
"env": {
"JORTT_CLIENT_ID": "your-client-id",
"JORTT_CLIENT_SECRET": "your-client-secret",
"JORTT_SCOPES": "invoices:read customers:read reports:read"
}
```
2026-07-11 19:10:55 +02:00
### Remote (Streamable HTTP)
With a static token, from Claude Code:
```bash
claude mcp add --transport http jortt https://jortt-mcp.example.com/mcp \
--header "Authorization: Bearer < token > "
```
With OIDC, add `https://jortt-mcp.example.com/mcp` as a custom connector on
claude.ai — the OAuth flow against your IdP is discovered automatically.
## NixOS
The repository is a flake providing a package and a NixOS module. The module
runs the server as a hardened systemd service (DynamicUser, syscall filtering,
read-only filesystem view) listening on loopback; put nginx/caddy in front for
TLS. Secrets are supplied via a systemd `EnvironmentFile` , which is where
[agenix ](https://github.com/ryantm/agenix ) comes in:
```nix
{
inputs.jortt-mcp.url = "github:deprekated/jortt-mcp";
# in your host configuration:
imports = [ inputs.jortt-mcp.nixosModules.default ];
age.secrets.jortt-mcp-env.file = ./secrets/jortt-mcp.env.age;
# The encrypted file contains:
# JORTT_CLIENT_ID=...
# JORTT_CLIENT_SECRET=...
# MCP_AUTH_TOKEN=... # token mode only
# MCP_OIDC_CLIENT_SECRET=... # only for introspection mode
services.jortt-mcp = {
enable = true;
environmentFile = config.age.secrets.jortt-mcp-env.path;
port = 3910;
# Simplest: static bearer token (set MCP_AUTH_TOKEN in the env file)
auth.mode = "token";
# Or: OAuth via your IdP / OAuth proxy, as needed for claude.ai
# auth.mode = "oidc";
# auth.oidc.issuer = "https://auth.example.com";
# auth.oidc.allowedSubjects = [ "kate" ];
# resourceUrl = "https://jortt-mcp.example.com/mcp";
allowedHosts = [ "jortt-mcp.example.com" ];
# readOnly = true;
# scopes = [ "invoices:read" "customers:read" "reports:read" ];
};
services.nginx.virtualHosts."jortt-mcp.example.com" = {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:3910";
# SSE responses must not be buffered:
locations."/".extraConfig = ''
proxy_buffering off;
proxy_read_timeout 1h;
'';
};
}
```
If your IdP issues opaque access tokens (Authelia does), switch the OIDC block
to introspection:
```nix
auth.oidc = {
issuer = "https://auth.example.com";
introspectionClientId = "jortt-mcp";
allowedSubjects = [ "kate" ];
};
# and put MCP_OIDC_CLIENT_SECRET=... in the agenix env file
```
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
## Tool overview
Tool names follow a predictable `verb_resource` convention derived from the API
path and version. A selection:
| Tool | Method & path | What it does |
| --------------------------- | ----------------------------------- | ----------------------------------------- |
| `list_customers` | `GET /customers` | List / search customers |
| `create_customers` | `POST /customers` | Create a customer |
| `get_customers` | `GET /customers/{customer_id}` | Fetch a single customer |
| `list_invoices` | `GET /invoices` | List / search invoices |
| `create_v3_invoices` | `POST /v3/invoices` | Create an invoice (invoice-level VAT) |
| `send_invoices` | `POST /invoices/{id}/send` | Send an invoice |
| `get_invoices_download` | `GET /invoices/{id}/download` | Get a download URL for the invoice PDF |
| `list_v2_estimates` | `GET /v2/estimates` | List estimates |
| `create_v2_estimates` | `POST /v2/estimates` | Create an estimate |
| `list_v3_expenses` | `GET /v3/expenses` | List expenses |
| `list_v3_bank_accounts` | `GET /v3/bank-accounts` | List bank accounts |
| `get_reports_summaries_profit_and_loss` | `GET /reports/summaries/profit_and_loss` | Dashboard profit & loss summary |
| `get_organizations_me` | `GET /organizations/me` | The organization tied to your credentials |
Newer resources are versioned (`v2` , `v3` ) and the version is part of the tool
name. Where Jortt offers multiple versions of the same operation (for example
invoice creation), prefer the highest version unless you have a reason not to —
`create_v3_invoices` supports invoice-level VAT.
Run the server and call `tools/list` to see the full, always-current set with
per-tool argument schemas.
## Development
```bash
npm run build # compile TypeScript to dist/ (and copy the spec)
npm run dev # tsc --watch
npm run typecheck # type-check without emitting
```
### Updating to the latest API
2026-07-11 19:10:55 +02:00
The tool set is generated from the committed OpenAPI spec. To pick up new
Jortt endpoints, refresh it and rebuild:
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
```bash
2026-07-11 19:10:55 +02:00
npm run fetch-spec # downloads https://api.jortt.nl/swagger_doc to src/openapi.json
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
npm run build
```
No code changes are needed — new operations become new tools automatically.
2026-07-11 19:10:55 +02:00
Review and commit the updated `src/openapi.json` .
## Security model
- **The MCP endpoint fronts your entire administration.** Whoever can call it
can do whatever the Jortt application's scopes allow. Layer the controls:
narrow the scopes at registration, set `JORTT_READ_ONLY` if writes aren't
needed, and in OIDC mode always set `MCP_OIDC_ALLOWED_SUBJECTS` .
- The HTTP transport refuses to start without `MCP_AUTH_MODE` ; there is no
unauthenticated mode.
- Static tokens are compared in constant time (over SHA-256 digests); JWTs are
validated for signature, issuer, expiry, and audience; introspection results
are cached for at most 60 s so revocation takes effect quickly.
- The server binds to loopback by default and expects a TLS-terminating
reverse proxy in front. `MCP_ALLOWED_HOSTS` /`MCP_ALLOWED_ORIGINS` add
DNS-rebinding protection.
- Secrets can be read from files (`*_FILE` ) instead of the environment; the
NixOS module keeps them in a root-owned `EnvironmentFile` (e.g. agenix) and
runs the service fully sandboxed.
- All outbound HTTP calls (Jortt API, token endpoint, JWKS/introspection)
carry timeouts. The OpenAPI spec that defines the tool surface is bundled at
build time, never fetched implicitly at runtime.
Add Jortt API MCP server
Model Context Protocol server that exposes the full Jortt accounting API
as MCP tools, generated from Jortt's OpenAPI specification.
- OAuth 2.0 client-credentials auth with token caching and 401 refresh
- 82 tools covering customers, invoices, estimates, expenses, projects,
bank accounts, reports, labels, ledger accounts and payroll
- Spec fetched at build time (with a runtime fetch fallback), so new API
operations become new tools automatically with no code changes
- Path/query/body parameters handled automatically; request bodies passed
under a single `body` argument with a resolved JSON Schema
- Readable, stable snake_case tool names derived from method, path and
operation summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016QhaFvGz9gfRg5qDV3ragu
2026-07-11 14:09:11 +00:00
## Notes & limits
- Jortt rate-limits the API to ~10 requests/second. List endpoints paginate at a
maximum of 100 items per page (`page` argument).
- This is an unofficial, community project and is not affiliated with or endorsed
by Jortt.
## License
MIT — see [LICENSE ](LICENSE ).