MCP server
MCP (Model Context Protocol) is how AI clients are given tools. schakl. serves one at /mcp: every
endpoint of the API is a tool there. You connect Claude Desktop, Claude Code or any other MCP client
to your own installation and ask your question in plain language, without exporting anything first.
What the AI may see and do is decided by the scopes on the key you hand it.
Where to find it
Section titled “Where to find it”The server runs at https://<your-host>/mcp, on your organisation’s own address. Its screen is
Instellingen → API en MCP (Settings → API and MCP): that is where you mint a key and get the
connection line for your client with it, where the clients you connected over OAuth are listed, and
where an admin creates OAuth clients with a fixed client id and secret. Keys that belong to nobody
live under Instellingen → Service-accounts (Settings → Service accounts).
Connecting without a key: OAuth
Section titled “Connecting without a key: OAuth”Claude (as a custom connector, in the browser or in Claude Desktop) and ChatGPT speak OAuth 2.1, and then need nothing but the address.
- In your client, add a custom connector with the address
https://<your-host>/mcp. For ChatGPT that ishttps://<your-host>/mcp/compact: the full list does not fit its budget. - The client registers itself and sends you to your own workspace’s sign-in screen. You sign in as always, with two-factor verification or your organisation’s SSO.
- On Toegang toestaan (Allow access) you choose what the client may do:
- Alles wat ik zelf mag (everything I may do): read and change, in every module you can reach;
- Alleen lezen (read only): everything you may read, never changing anything;
- Zelf kiezen (choose myself): a list you tick yourself, grouped per module.
- Press Toestaan (Allow). No secret is copied anywhere.
What that flow hands back is an ordinary personal API key, not a second kind of access: every request is capped by your live permissions and your client selection. The first two choices are stored as a rule, not as today’s list: a module switched on later is included by itself, because a chat client never asks for consent again. Zelf kiezen stays a fixed list; a new module only joins when you reconnect.
Your connections are listed under Gekoppelde clients (connected clients) on Instellingen → API en MCP. Ontkoppelen (disconnect) ends every session of that client, and it cannot refresh itself.
A client that asks for a client id and secret
Section titled “A client that asks for a client id and secret”Claude needs no client id. Tools that do not register themselves, such as an enterprise connector
config or an automation tool, do ask for a fixed client id and secret. An admin holding
settings.oauth.manage creates one under OAuth-clients on the same screen: a name and one
redirect URI per line. The secret is shown exactly once. The same card also lists the clients that
registered themselves, marked apart, with Nieuw secret (new secret; only for clients created
here) and Intrekken (revoke), which switches the client off along with every key it ever issued,
whoever’s.
Connecting with a key
Section titled “Connecting with a key”-
Open Instellingen → API en MCP. Under Wat koppel je? (what are you connecting) choose AI-assistent (MCP).
-
Give the key a Naam (name) and choose under Wat mag die doen? (what may it do): Alleen lezen (read only, the default and usually enough), Lezen en schrijven (read and write), or Kies per recht (per permission).
-
Choose the section that fits your client under Welke tools (which tools; see below). Leave Verloopt op (expires on) empty for a key that never expires and press Sleutel aanmaken.
-
The screen prints the connection line with the key already in it. For Claude Code:
Terminal window claude mcp add --transport http schakl https://<your-host>/mcp \--header "Authorization: Bearer schakl_…"Copy it straight away: the key is shown only once.
-
Any other client that speaks Streamable HTTP: point it at the same address with the header
Authorization: Bearer schakl_…orX-API-Key: schakl_…. -
Ask the client to list its tools. If nothing appears, check that you used your organisation’s own address — a key only exists on its own organisation’s hostname.
What tools the AI gets
Section titled “What tools the AI gets”The tool list is derived straight from the API itself, so it never lags behind. Every endpoint under
/api/v1 is a tool, named after the operation: list_companies, create_task, time_summary.
Only the modules your organisation has switched on have endpoints, so the tool list automatically follows which modules you use. Turn a module off and its tools disappear.
A few things are deliberately left out:
| Not a tool | Why |
|---|---|
| Signing in and the first-run wizard | session handling, meaningless for a key |
| The instance operator’s administration surface | that belongs to the instance owner, not the organisation |
| Import: inspecting a file and importing it | those need a file, plus the human judgement of mapping columns |
The rest of import and export stays available: reading an entity’s columns and exporting data are both useful to an AI.
Too many tools for a chat client
Section titled “Too many tools for a chat client”The full surface is ~620 tools, and that is the right default for a coding agent, which reads the list once. It is the wrong one for a chat client, which puts every tool in the model’s context on every turn and therefore budgets: ChatGPT allows 5,000 tokens for all tools together, and the full list is around 527,000. No amount of trimming closes that gap — at that count you have about 85 tokens per tool, which does not buy a name. Only fewer tools works.
So the same server answers on narrower URLs, and you paste whichever one fits the client:
| URL | What it offers |
|---|---|
/mcp | Everything. The default, and what a coding agent wants |
/mcp/compact | A curated read-only fourteen, small enough for a chat client’s ceiling |
/mcp/<module> | One module’s own endpoints, for example /mcp/google-ads or /mcp/invoicing |
/mcp/<bundle> | A named job spanning several modules (see below) |
| Bundle | Modules |
|---|---|
/mcp/agent | Clients, contacts, projects, tasks, hours, contact moments |
/mcp/infra | Domains, websites, hosting, Cloudflare, OXXA, uptime, WordPress |
/mcp/finance | Invoicing, subscriptions, Mollie |
/mcp/growth | Google Ads, Google Analytics, Search Console, Tag Manager, marketing, reporting |
Google Analytics, Search Console and WordPress also have a section of their own
(/mcp/google-analytics, /mcp/google-search-console, /mcp/wordpress): an agent asked to work a
client’s Analytics wants seventeen tools, not the hundred of the whole bundle.
A module section is derived from that module’s own routes, so an endpoint added tomorrow is served tomorrow; a bundle names modules, never tools, which keeps it as self-maintaining as the sections it unions. A URL that names nothing is refused and tells you what does exist, rather than quietly falling back to all 620 — that would look like it worked and could not be spotted by reading.
What the AI may and may not do
Section titled “What the AI may and may not do”A tool call travels exactly the same path as an ordinary request from a screen: hostname to organisation, row-level isolation in the database, then that endpoint’s own permission check. There is no second route to the data. A call the key holds no permission for simply comes back as an error on the tool.
Do watch what you tick: the tool surface includes writes, because it is the whole API. If you want the AI to read along only, give the key read permissions only. That is a choice you make when minting the key, not something the server enforces for you.
Permissions
Section titled “Permissions”| Permission | What it opens | Default |
|---|---|---|
apikeys.personal.manage | Manage own API keys | Administrator, Member |
apikeys.service_account.manage | Manage service accounts | Administrator |
settings.oauth.manage | Manage OAuth clients with a fixed client id and secret | Administrator |
After that the key decides everything: each ticked scope is a permission from the same catalogue as Instellingen → Rollen (Settings → Roles). A personal key is additionally re-capped to its owner’s live permissions on every single request.
Good to know
Section titled “Good to know”- An anonymous call gets a 401, with the invitation to do OAuth. That is how a client discovers the server, by being refused, and it means a stranger cannot see which modules you run.
- The transport is Streamable HTTP with self-contained JSON responses. The older SSE transport is not offered.
- 600 calls per minute per key. An AI that searches enthusiastically can run into that.
- Intrekken (Revoke) in the key screen removes access immediately, with nothing to change on the client side.
- The tool list comes entirely from the API description. Hand-written, smarter tools (such as “find this client”, or a client’s performance with the comparison already worked out) do exist inside the in-app assistant, but are not part of this list.
- A route that takes a file as multipart is not a tool. Where it matters there is a JSON twin, such as uploading an attachment as base64.
- To not serve the server at all, set
SCHAKL_MCP_ENABLED=falseon the server. That removes/mcpentirely.