Zo bouwde ik een MCP-server die Claude laat werken met Exact Online | Forrict Ga naar hoofdinhoud
AI Bouwverslag

Zo bouwde ik een MCP-server die Claude laat werken met Exact Online

Fons Biemans
Zo bouwde ik een MCP-server die Claude laat werken met Exact Online
Een bouwverslag over mijn eigen MCP-server voor Exact Online: 65 tools, inloggen via OAuth, draaien op AWS Lambda en vangnetten zodat Claude nooit zomaar iets boekt. Met de echte code en de lessen.

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:

DatumMijlpaal
8 juli 2026Eerste versie: OAuth, de API-client en 28 tools voor relaties, financiën, verkoop, inkoop en logistiek
24 september 202628 tools erbij, “insight”-tools die meerdere API-calls in één antwoord combineren, en vangnetten voor schrijfacties
30 september 2026Elke 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.

OnderdeelWat Claude ermee kan
RelatiesKlanten en contactpersonen zoeken, aanmaken en bijwerken
FinancieelGrootboekrekeningen, dagboeken, boekingsregels, openstaande debiteuren en crediteuren, ouderdomsanalyse, boekperiodes
RapportageWinst-en-verlies en balans, budgetten, bankboekingen, betalingen, btw-codes
Verkoop en inkoopVerkoopfacturen en -orders, inkoopboekingen en -orders
OverigOffertes, kansen, documenten, de digitale postbus, projecten en uren, medewerkers, abonnementen, artikelen en voorraad
InsightsEé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 queryexact_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

Ontwerp: Claude praat MCP met de server, de server praat met de REST API van Exact Online, lokaal via stdio of in AWS Lambda achter een eigen OAuth-server

LokaalAWS Lambda
Voor wieClaude Desktop en Claude Code op je eigen computerClaude op claude.ai en Claude Code, zonder dat er iets op je computer draait
VerbindingstdioMCP Streamable HTTP via een Lambda Function URL
TokensEen bestand op je computer (alleen voor jou leesbaar)AWS Secrets Manager
InloggenEenmalig npm run authBij 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:

  1. Claude roept /mcp aan zonder token en krijgt een 401. In de metadata vindt hij waar hij moet inloggen.
  2. Claude stuurt je naar /authorize. Die stuurt je door naar de login van Exact Online, inclusief de tweestapsverificatie van Exact.
  3. Na het inloggen controleert de server of je de Exact-gebruiker bent die toegang heeft. Ben je iemand anders, dan krijg je een 403 en wordt er niets opgeslagen.
  4. 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

F

Fons Biemans

AWS expert en consultant bij Forrict, gespecialiseerd in cloud architectuur en AWS best practices voor Nederlandse bedrijven.

Tags

Exact Online MCP Model Context Protocol AWS Lambda Claude

Blijf op de hoogte

Ontvang AWS-inzichten en tips in je inbox.

Blijf op de hoogte

Ontvang AWS-inzichten en tips in je inbox.

Gerelateerde Artikelen

Wat Claude voor mijn boekhouding in Exact Online doet

Wat Claude voor mijn boekhouding in Exact Online doet

Hoe ik Claude gebruik voor de boekhouding van drie bv's in Exact Online: vragen stellen, een groepsrapport met prognose, missende facturen opsporen, een kwartaal afsluiten en verkoopfacturen klaarzetten. Met een voorbeeldrapport en de regels die ervoor zorgen dat Claude nooit zomaar iets boekt.

Lees meer