Hoe iedereen van Natuurhuisje het dataplatform vragen kan stellen in gewone taal en gecontroleerde antwoorden krijgt, zonder SQL te schrijven, exports naar spreadsheets te maken of toegang te hebben tot gevoelige informatie.
TLDR: we hebben Cube Core op de gold-laag in BigQuery gezet, beschikbaar via een MCP-server met twee tools, en als organisatiebrede Claude-plugin uitgerold naar alle collega’s. De plugin draait onder de identiteit van de gebruiker en kan geen ruwe SQL genereren. Elke medewerker logt in met een eigen Google-account, dus elke query draait als diegene zelf, en de dynamische data masking van BigQuery bepaalt wat diegene ziet.

Een agent zou nooit zelf SQL moeten schrijven
Een LLM op een datawarehouse loslaten en zelf SQL laten schrijven is meestal een slecht idee. Hoe logisch dat ook klinkt, we zien het nog steeds gebeuren. Het is namelijk heel makkelijk en snel op te zetten: je koppelt een LLM gewoon aan de ruwe tabellen van een database.
Een collega van de klantenservice die wil weten hoeveel boekingen er vorig kwartaal zijn geannuleerd, per type huisje, kan simpelweg een LLM wat SQL laten schrijven. Een LLM met toegang tot tabellen koppelt de boekingsfeittabel aan een SCD2-historie, telt elke boeking die ooit van status is veranderd dubbel, en presenteert het resultaat met net zoveel vertrouwen als het correcte antwoord. Daarnaast leest de agent de inhoud van kolommen, en bij Natuurhuisje betekent dat namen, e-mailadressen en adressen van gasten.
De alternatieven die we vaak zien zijn niet veel beter. Traditioneel gaan dit soort vragen naar het datateam en komt het antwoord dagen later binnen. Of erger: iemand exporteert een CSV rechtstreeks uit Power BI om die met een eigen agent te gebruiken, en dan bestaan er opeens twee versies van de waarheid. Echte analytics as a service betekent dat iedereen een vraag kan stellen en altijd hetzelfde antwoord terugkrijgt als het gevalideerde dashboard laat zien.
Betere prompts lossen dit niet op. Je lost dit op door modellen helemaal geen directe toegang tot tabellen te geven. Een goed gedefinieerde semantische laag bovenop je data, waarin elke metric en KPI vooraf is gedefinieerd en alle velden met goede metadata zijn beschreven, is de basis van elke oplossing voor analytics as a service.
Dit is al goed gemeten en toegepast
Het datascienceteam van Anthropic publiceerde hoe ze dit zelf hebben opgezet. Zonder gestructureerde context en semantische laag beantwoordde Claude hun analytics-evaluaties in 21% van de gevallen correct. Met zo’n laag scoorde het consequent boven de 95%. Het verschil zat in canonieke datasets in plaats van veertig plausibele kandidaattabellen, een semantische laag, en procedurele kennis vastgelegd in skills.
LangChain kwam uit op dezelfde stack: dbt-modellen met duidelijke beschrijvingen en een semantische laag, met daarbovenop handleidingen en signalen over welke bronnen betrouwbaar zijn. Hun datateam van drie personen verwerkt, met hulp van agents, nu ongeveer veertig keer zoveel verzoeken als voorheen. Zij concluderen: toegang voor agents tot data vergroot de noodzaak van juiste modellering van data. LLM’s versterken alles wat je ze geeft, zowel juiste als onjuiste informatie.
We bouwden een semantische laag op de gold-laag in BigQuery
Het dataplatform van Natuurhuisje draait op GCP: dlt voor extractie, dbt voor het modelleren naar bronze, silver en gold, en Dagster voor orkestratie op hun eigen Kubernetes-cluster. Bovenop de gold-laag zit Cube Core, waar alle metrics en KPI’s zijn gedefinieerd met een vaste SQL-definitie, een naam en een beschrijving van het gebruik. cancellation_rate is cancelled_bookings / total_bookings, één keer vastgelegd in YAML en gereviewd in een merge request, net als alle andere platformcode.
Zo’n vastgelegde definitie is voor een model veel belangrijker dan voor een mens. Een mens die cancellation_rate net iets anders berekent, merkt meestal dat er iets niet klopt aan het resultaat en past de SQL-query waar nodig aan. Een agent doet dat niet (zonder extra context).
We bouwden een eenvoudige FastMCP-server bovenop Cube die twee tools aanbiedt. describe_data geeft de catalogus terug: welke cubes, measures en dimensions er bestaan, met hun beschrijvingen. read_data voert een Cube-query uit en geeft rijen uit BigQuery terug. Een agent die een vraag niet in measures en dimensions kan uitdrukken, moet dat zeggen. Dit is veel nuttiger dan een foutief resultaat terugkrijgen. Daarom leveren we de MCP-server altijd samen met een bijpassende skill die duidelijke regels en grenzen voor de agent vastlegt.

Je persoonlijke token reist helemaal naar BigQuery
De makkelijke manier om dit uit te rollen is de MCP-server met een vast serviceaccount verbinding te laten maken met Cube en BigQuery. Dat werkt meteen, maar breekt elke selectieve toegangsregel: iedereen ziet alles wat het serviceaccount kan opvragen, en op dit platform zijn dat ook gevoelige persoonsgegevens (PII) van gasten.
Daarom authenticeert iedereen zich bij de MCP-server als zichzelf, met Google OAuth ingebouwd:
- Je Claude-client start de OAuth-flow. De server ondersteunt DCR, dus niemand hoeft een client-ID of secret in een instellingenscherm te plakken.
- Je logt in met je eigen Google-account van Natuurhuisje. Alleen e-mailadressen van het bedrijfsdomein komen erdoor: de hosted domain-claim op het token wordt aan de serverkant gecontroleerd, zowel bij het ophalen van de toollijst als bij elke tool-aanroep. Een extern account ziet het model niet eens, omdat de lijst met cubes zelf al informatie is.
- Je token met BigQuery-scope gaat mee in de security context van Cube. Cube bouwt zijn BigQuery-client met jouw token, niet met het serviceaccount van de Cube-pod, dus de query draait onder jouw identiteit.
- BigQuery bepaalt wat je ziet. Policy tags en dynamic data masking, per kolom gekoppeld door dbt, geven leesbare waarden aan mensen die daar rechten voor hebben en
CONFIDENTIALaan alle anderen.
Masking wordt beoordeeld op basis van de principal die de query uitvoert, dus het beleid dat al geldt voor BigQuery geldt ook in de chat. Zo hoeven we geen extra beveiligingsregels in Cube toe te voegen. Dat zou dubbel werk zijn, met extra onderhoud en het risico dat de twee uit elkaar gaan lopen.

Beschikbaar als één samenhangend pakket in plaats van losse onderdelen
-
Cube leest de metadata van dbt-modellen niet. De
persist_docs-instelling van dbt zet kolom- en tabelbeschrijvingen in BigQuery. Cube hoort die beschrijvingen niet nog eens handmatig in zijn YAML-bestanden te definiëren, maar heeft ze wel nodig om goed met een agent samen te werken. Daarom zetten we in een CI-stap alle kolom- en tabelbeschrijvingen van dbt door naar Cube. -
Een semantische laag vertelt een agent wat er bestaat, niet hoe die zich moet gedragen. Daarom is het belangrijk om je MCP-server en semantische laag altijd te bundelen met een skill die duidelijk de grenzen en regels vastlegt. Skills brachten Anthropic van 21% naar boven de 95%. Die van ons staat naast het Cube-model, dus een gewijzigde definitie en de bijbehorende regels komen in dezelfde merge request terecht.
-
Niet-technische collega’s draaien geen CLI-commando’s. De MCP-server, de verbinding en de skill zijn als één Claude-plugin beschikbaar via de plugininstellingen van de organisatie. Installeren, één keer inloggen met OAuth, en de tools staan klaar in Claude Code, chat en Cowork, zonder ooit een CLI-commando te draaien.
Chat en Cowork loggen in vanaf de servers van Anthropic
Dat laatste onderdeel bleek iets ingewikkelder dan gedacht. De integratie met Claude Code werkte meteen. Claude Chat en Cowork faalden met “couldn’t register with the sign-in service” en verder niets.
De verschillende chatomgevingen maken niet vanaf dezelfde plek verbinding. Claude Code doet OAuth-discovery en clientregistratie vanaf je laptop op een vertrouwd netwerk. Chat en Cowork maken eigenlijk geen verbinding vanaf je laptop; die draaien op de servers van Anthropic.
De host staat achter een CDN waarvan de regels een inlogpoging vanaf een lokale laptop doorlieten en een onbekende IP-range van een datacenter tegenhielden, precies zoals het hoort. Het ophalen van de OAuth-metadata van buiten het bedrijfsnetwerk gaf een geldig antwoord, wat voelde als bewijs maar dat niet was: de laptop kwam door dezelfde regel waar de servers van Anthropic op strandden. Een verzoek dat aan de edge wordt geweigerd, bereikt de ingress controller of de MCP-pod nooit, dus geen van beide logt het.
De oplossing was één regel in het CDN die de IP-restrictie alleen overslaat voor het MCP-endpoint, de vijf OAuth-routes en de twee discovery-documenten. Al het andere op die hostname blijft erachter.
(http.host eq "<your MCP host>") and (
http.request.uri.path eq "/mcp"
or starts_with(http.request.uri.path, "/mcp/")
or http.request.uri.path in {"/authorize" "/token" "/register" "/consent" "/auth/callback"}
or http.request.uri.path eq "/.well-known/oauth-authorization-server"
or http.request.uri.path eq "/.well-known/oauth-protected-resource/mcp"
)
Die lijst met paden bestaat nu twee keer: in het CDN en in de Ingress. Voeg je aan de ene kant een route toe en vergeet je de andere, dan heb je weer een fout die niets logt. Rol je een MCP-server uit voor een hele organisatie, test dan elke laag voor je cluster vanaf de plek waar het inloggen echt vandaan komt.
Zo ziet het eruit voor de collega van de klantenservice
Een collega van de klantenservice opent Claude en stelt de vraag uit het begin van deze post. Claude roept describe_data aan, vindt de measure en dimension die het nodig heeft, en voert read_data uit. Het antwoord komt terug met de naam van de measure, de filters en de periode naast het getal, zodat iedereen het kan vergelijken met het dashboard.
Catalogus gelezen, measure gevonden, query uitgevoerd als die specifieke persoon, masking toegepast door BigQuery. Het model schrijft nooit SQL, en het getal komt overeen met het dashboard.
Wolk heeft fantastisch werk geleverd bij de opzet van ons nieuwe dataplatform. Ze combineerden hun enorme expertise in data-engineering met een fijne nuchtere aanpak, waarbij ze hun kennis direct en praktisch hebben overgedragen op ons eigen team. Dankzij deze samenwerking hebben we een grote en belangrijke stap gezet in datazelfstandigheid bij Natuurhuisje en hebben we zelf de touwtjes in handen om verder te innoveren.
– Pieter van der Hammen, CPTO bij Natuurhuisje
Dit concept werkt niet enkel op BigQuery
We bouwden dit op BigQuery omdat de data van Natuurhuisje daar staat, maar niets in het patroon is daarvan afhankelijk. Cube Core is open source en er zijn meerdere alternatieven, MCP is een open protocol, en dezelfde opzet werkt op Trino met Iceberg en elke OIDC-provider.
Wil je analytics as a service op je eigen stack? Neem contact op. Bij Wolk zijn we gespecialiseerd in open-source en soevereine Europese dataoplossingen.




