In deel 1 liet ik zien wat Claude voor mijn boekhouding doet: een groepsrapport, missende facturen opsporen, een kwartaal afsluiten en verkoopfacturen klaarzetten. Dat kan omdat Claude rechtstreeks in Exact Online kan kijken, via een MCP-server die ik daarvoor zelf heb gebouwd. Dit deel gaat over die server: hoe hij in elkaar zit en wat er onderweg tegenviel. De volledige code staat openbaar op GitHub.
Wat is MCP ook alweer?
Het Model Context Protocol (MCP) is een open standaard waarmee een AI-assistent zoals Claude tools in andere systemen kan aanroepen. Een MCP-server geeft een lijst met tools door, elk met een naam, een beschrijving en de parameters die hij verwacht. Bij elke vraag kiest Claude zelf welke tool past.
Mijn server is dus een vertaler. Naar Claude toe praat hij MCP, naar Exact toe de REST API van Exact Online.
Van 28 naar 65 tools
Ik bouwde de server samen met Claude, in TypeScript met de officiële MCP SDK. De eerste versie stond er in juli, daarna kwamen er twee rondes bij:
| Datum | Mijlpaal |
|---|---|
| 8 juli 2026 | Eerste versie: OAuth, de API-client en 28 tools voor relaties, financiën, verkoop, inkoop en logistiek |
| 24 september 2026 | 28 tools erbij, “insight”-tools die meerdere API-calls in één antwoord combineren, en vangnetten voor schrijfacties |
| 30 september 2026 | Elke schrijftool standaard in proefmodus, en een administratie kiezen op naam in plaats van op nummer |
Inmiddels zijn het 65 tools. 56 daarvan lezen alleen, 9 maken iets aan of wijzigen iets.
| Onderdeel | Wat Claude ermee kan |
|---|---|
| Relaties | Klanten en contactpersonen zoeken, aanmaken en bijwerken |
| Financieel | Grootboekrekeningen, dagboeken, boekingsregels, openstaande debiteuren en crediteuren, ouderdomsanalyse, boekperiodes |
| Rapportage | Winst-en-verlies en balans, budgetten, bankboekingen, betalingen, btw-codes |
| Verkoop en inkoop | Verkoopfacturen en -orders, inkoopboekingen en -orders |
| Overig | Offertes, kansen, documenten, de digitale postbus, projecten en uren, medewerkers, abonnementen, artikelen en voorraad |
| Insights | Eén call voor een P&L-overzicht, een check op missende boekingen, de status van de bankkoppelingen, de postbus met bijlagen, voorstellen om betalingen aan facturen te koppelen, en een geconsolideerde P&L over meerdere administraties |
| Vrije query | exact_odata_query voor elk endpoint waar nog geen eigen tool voor is |
Aan de insight-tools heb ik het meeste. Voor een vraag als “mis ik ergens boekingen?” zijn anders zes losse calls per administratie nodig. Nu zet één tool de negatieve debiteuren en crediteuren, de open posten op tussenrekeningen en het aantal facturen per maand naast elkaar. Een gat zie je dan meteen.
Twee manieren om hem te draaien
| Lokaal | AWS Lambda | |
|---|---|---|
| Voor wie | Claude Desktop en Claude Code op je eigen computer | Claude op claude.ai en Claude Code, zonder dat er iets op je computer draait |
| Verbinding | stdio | MCP Streamable HTTP via een Lambda Function URL |
| Tokens | Een bestand op je computer (alleen voor jou leesbaar) | AWS Secrets Manager |
| Inloggen | Eenmalig npm run auth | Bij het koppelen van de connector log je in met je Exact-account |
Zelf gebruik ik de Lambda-versie, als eigen connector in Claude. Op mijn eigen computer hoeft dan niets te draaien. De hele omgeving is één AWS CDK-stack. Daarin zitten een Lambda op Node.js 22, één secret, CloudWatch-alarmen voor mislukte logins en fouten, en een wekelijkse EventBridge-regel die het Exact-token ververst. Die regel zorgt dat het token niet verloopt als ik Claude een tijd niets over Exact vraag.
Inloggen: twee keer OAuth
Een server op internet die bij je boekhouding kan, moet je goed dichtzetten. De MCP-specificatie schrijft daarvoor OAuth 2.1 voor. In mijn Lambda zit daarom een kleine eigen OAuth-server, die het inloggen op zijn beurt doorzet naar Exact Online. Vandaar: twee keer OAuth.
Het koppelen gaat zo:
- Claude roept
/mcpaan zonder token en krijgt een401. In de metadata vindt hij waar hij moet inloggen. - Claude stuurt je naar
/authorize. Die stuurt je door naar de login van Exact Online, inclusief de tweestapsverificatie van Exact. - Na het inloggen controleert de server of je de Exact-gebruiker bent die toegang heeft. Ben je iemand anders, dan krijg je een
403en wordt er niets opgeslagen. - Claude wisselt de code (met client secret en PKCE) in voor een token dat een uur geldig is en een refreshtoken van 30 dagen.
Er is maar één vooraf geregistreerde client, en de server accepteert alleen de callback-adressen van Claude. Wil ik alle uitgegeven tokens in één keer intrekken, dan verander ik de ondertekeningssleutel in het secret.
Veilig schrijven: Claude boekt nooit zomaar
Lezen is onschuldig, schrijven niet. Voor alle 9 schrijftools gelden daarom dezelfde regels.
De belangrijkste is de proefmodus. Elke schrijftool staat standaard op dry_run: true en laat dan alleen zien wat hij naar Exact zou sturen. Pas met dry_run=false gebeurt het echt. Daarnaast maakt de server alleen concepten. Memoriaalboekingen, inkoopboekingen en verkoopfacturen komen als concept in Exact binnen, en definitief maken doe ik zelf, in Exact.
Verder krijgt elke tool een MCP-annotatie die zegt of hij alleen leest. Zo weet Claude wanneer het om bevestiging moet vragen. Met één instelling zet ik de server in alleen-lezenmodus, en dan verdwijnen alle schrijftools uit de lijst. Elke schrijfactie komt als regel in de logs, op Lambda in CloudWatch. Querystrings, de inhoud van verzoeken en tokens logt de server nooit.
De annotaties staan op één centrale plek. Elke tool waarvan de naam begint met exact_create_ of exact_update_ telt als schrijftool:
const writes = WRITE_TOOL_RE.test(name);
if (writes && readOnly) return undefined; // alleen-lezenmodus: tool niet registreren
return register(name, {
...config,
annotations: {
readOnlyHint: !writes,
destructiveHint: false,
idempotentHint: !writes,
openWorldHint: true,
...config.annotations,
},
}, cb);
Zo’n annotatie is alleen een hint. De MCP-specificatie zegt zelf dat een client er niet blind op moet vertrouwen. De echte bescherming zit daarom in de proefmodus en in het feit dat er alleen concepten ontstaan.
Wat ik onderweg leerde
De belangrijkste les ging over tokens. Een access token van Exact is 10 minuten geldig. Bij elke refresh krijg je een nieuw refreshtoken en vervalt het oude meteen. Twee refreshes tegelijk, en je bent buitengesloten.
Daarom slaat de server het nieuwe token op voordat iemand het access token gebruikt, en wachten refreshes op elkaar. Draaien er twee Lambda’s tegelijk, dan leest de server de opslag opnieuw en neemt hij het token van de ander over. Een gemiste refresh betekent opnieuw inloggen, een dubbele refresh betekent buitengesloten. Dit is het stuk code waar het om draait:
private async doRefresh(): Promise<TokenSet> {
// Een ander proces kan de tokens al ververst hebben.
let current = await this.loadFromStore();
if (this.isFresh(current)) return current;
let next: TokenSet;
try {
next = await this.refreshWith(current);
} catch (err) {
// Race verloren: een ander proces gebruikte ons refreshtoken al.
if ((err as OAuthError).code !== "invalid_grant") throw err;
const latest = await this.loadFromStore();
if (latest.refresh_token === current.refresh_token) throw err;
if (this.isFresh(latest)) return latest;
current = latest;
next = await this.refreshWith(current);
}
// Eerst het nieuwe, eenmalige refreshtoken opslaan, dan pas gebruiken.
await this.store.save(next);
return next;
}
De API van Exact had ook zo zijn eigenaardigheden. Als ik $top meestuurde, kwamen er bij sommige endpoints, zoals financial/ReportingBalance, lege lijsten terug. De server stuurt $top nu niet meer door, pagineert zelf via de __next-links en waarschuwt als een resultaat leeg is. Zo gaat het in elk geval niet meer stil mis.
Exact begrenst ook het aantal calls per minuut en per dag. Bij een 429 wacht de server zo lang als Retry-After aangeeft en probeert hij het één keer opnieuw. Elke lijst laat zien hoeveel calls er deze minuut nog over zijn.
En Exact gebruikt OData in de oude stijl, met datums als /Date(1735689600000)/. De server zet die om naar gewone ISO-datums en haalt __metadata en lege velden uit elk antwoord. Dat scheelt Claude een hoop tekst om door te lezen.
Dan nog twee kleinere dingen. In Exact kies je een administratie op nummer. Bij mijn server kan het ook op naam, of op een deel van de naam. De lijst met administraties haalt hij één keer op en onthoudt hij. En het redirect-veld in het Exact App Center knipt lange adressen af. Bij mij gebeurde dat na 45 tekens, dus kijk na het opslaan goed wat er echt in dat veld staat.
Wat ik ervan meeneem
De 65 tools waren het makkelijke deel. De meeste tijd ging zitten in alles eromheen: inloggen, tokens die maar één keer werken, de limieten van de API en de vangnetten rond schrijven. Dat geldt niet alleen voor Exact. Elk systeem met een API kun je zo aan Claude koppelen, als je vooraf goed nadenkt over wie erbij mag en wat er mag veranderen.
Wil je de server zelf draaien? In de repository op GitHub staat stap voor stap hoe je een app registreert in het Exact App Center en de server start, op je eigen computer of op AWS Lambda. De code valt onder de PolyForm Small Business License. Bedrijven met minder dan 100 mensen en minder dan 1 miljoen dollar omzet per jaar mogen hem gebruiken. Grotere organisaties kunnen een commerciële licentie bij mij afnemen.
Wat ik er in de praktijk mee doe, staat in deel 1.
Werk je met Exact Online en wil je ook zo’n koppeling? Ik help mkb-bedrijven hun workflows te automatiseren en geef training aan teams die met AI willen bouwen. Stuur me een bericht, dan kijken we samen wat er bij jou kan.
Bronnen
- De code van de server: Forrict/mcp-exactonline op GitHub
- Model Context Protocol: specificatie, autorisatie en tools en annotaties, TypeScript SDK
- Exact Online: REST API-resources, nieuwe access tokens ophalen, API-limieten
- Levensduur van tokens en eenmalige refreshtokens, uitgelegd door een moderator: Thinkwise Community
- Anthropic: een eigen connector toevoegen in Claude, MCP in Claude Code
- AWS: AWS CDK, Lambda Function URLs
Fons Biemans
AWS expert en consultant bij Forrict, gespecialiseerd in cloud architectuur en AWS best practices voor Nederlandse bedrijven.