---
title: "MCP of CLI is de verkeerde vraag. Vraag waar de state woont."
description: "Tobi Lütke zegt dat MCP versus CLI op de verkeerde laag wordt besproken: beide werken zodra de state tussen stappen bewaard blijft. Eens. Voor een sync is die state de eigen records van de integratie, niet de heap van een REPL, en daarom bewaren de MCP-tools van Tajo niets in een sessie."
url: https://tajo.io/nl/blog/mcp-or-cli-is-the-wrong-question/
markdown: https://tajo.io/nl/blog/mcp-or-cli-is-the-wrong-question.md
lang: nl-NL
author: "Set Noa"
author_url: https://tajo.io/nl/set-noa/
published: 2026-11-04
updated: 2026-11-05
alternates: {en-US: /blog/mcp-or-cli-is-the-wrong-question.md, bg-BG: /bg/blog/mcp-or-cli-is-the-wrong-question.md, cs-CZ: /cz/blog/mcp-or-cli-is-the-wrong-question.md, de-DE: /de/blog/mcp-or-cli-is-the-wrong-question.md, da-DK: /dk/blog/mcp-or-cli-is-the-wrong-question.md, es-ES: /es/blog/mcp-or-cli-is-the-wrong-question.md, fr-FR: /fr/blog/mcp-or-cli-is-the-wrong-question.md, el-GR: /gr/blog/mcp-or-cli-is-the-wrong-question.md, hi-IN: /hi/blog/mcp-or-cli-is-the-wrong-question.md, id-ID: /in/blog/mcp-or-cli-is-the-wrong-question.md, hr-HR: /hr/blog/mcp-or-cli-is-the-wrong-question.md, hu-HU: /hu/blog/mcp-or-cli-is-the-wrong-question.md, it-IT: /it/blog/mcp-or-cli-is-the-wrong-question.md, ja-JP: /jp/blog/mcp-or-cli-is-the-wrong-question.md, ko-KR: /kr/blog/mcp-or-cli-is-the-wrong-question.md, ar-SA: /ar/blog/mcp-or-cli-is-the-wrong-question.md, no-NO: /no/blog/mcp-or-cli-is-the-wrong-question.md, pl-PL: /pl/blog/mcp-or-cli-is-the-wrong-question.md, pt-BR: /pt/blog/mcp-or-cli-is-the-wrong-question.md, ro-RO: /ro/blog/mcp-or-cli-is-the-wrong-question.md, ru-RU: /ru/blog/mcp-or-cli-is-the-wrong-question.md, sv-SE: /se/blog/mcp-or-cli-is-the-wrong-question.md, zh-CN: /zh-cn/blog/mcp-or-cli-is-the-wrong-question.md, sl-SI: /sl/blog/mcp-or-cli-is-the-wrong-question.md, sr-Latn-RS: /sr/blog/mcp-or-cli-is-the-wrong-question.md, th-TH: /th/blog/mcp-or-cli-is-the-wrong-question.md, tr-TR: /tr/blog/mcp-or-cli-is-the-wrong-question.md, ur-PK: /ur/blog/mcp-or-cli-is-the-wrong-question.md, vi-VN: /vi/blog/mcp-or-cli-is-the-wrong-question.md, sk-SK: /sk/blog/mcp-or-cli-is-the-wrong-question.md}
---

[AI-agents](https://tajo.io/nl/blog/?category=ai-agents)

# MCP of CLI is de verkeerde vraag. Vraag waar de state woont.

Tobi Lütke zegt dat MCP versus CLI op de verkeerde laag wordt besproken: beide werken zodra de state tussen stappen bewaard blijft. Eens. Voor een sync is die state de eigen records van de integratie, niet de heap van een REPL, en daarom bewaren de MCP-tools van Tajo niets in een sessie.

mcp vs cli

Moet een AI-agent MCP of een CLI gebruiken?

Lütke heeft gelijk dat MCP versus CLI de verkeerde laag is: beide werken zodra state tussen stappen blijft bestaan, en bash wint alleen omdat het bestandssysteem die state is. Voor een sync is de state echter niet de heap van een REPL. Het zijn de eigen records van de integratie: een cursor, een openstaand voorstel, een lease, een plan. Daarom bewaren de MCP-tools van Tajo niets in een sessie: elke agent, vanuit elke client, gaat verder vanaf de records. Tajo voert vandaag geen klantcode uit en heeft geen REPL, en dat zeggen we hieronder ronduit.

[Meer informatie](https://tajo.io/nl/blog/mcp-or-cli-is-the-wrong-question/#de-laag-die-hij-bedoelt)

Op 21 september 2026, om 00.33 uur UTC, plaatste Tobi Lütke dit: “MCP vs CLI voor LLM’s is de verkeerde discussie. Mensen debatteren op de verkeerde laag.” [Op 23 oktober](https://x.com/tobi/status/2113596638899974567) wees hij daarna op een nieuwe repository genaamd [dquickts](https://github.com/stencil-hq/dquickts). Het is een fork van QuickJS die een programma midden in een `for`-lus kan pauzeren, de hele heap naar bytes kan schrijven en het in een ander proces kan hervatten. “Uitstekende code-mode-omgeving,” schreef hij.

Hij heeft gelijk over de laag. Ik wil het eerst met hem eens zijn, in zijn eigen woorden, en de bewering daarna versmallen tot ze iets zegt waar een sync mee verder kan.

## De laag die hij bedoelt

Hier is het argument zoals hij het maakte, want dat is de enige eerlijke manier om een argument te citeren dat je wilt ombuigen:

> Beide werken ongelooflijk goed, zolang ze draaien via een REPL-achtige omgeving die state kan bewaren. Het grappige aan CLI is dat CLI-tools meestal via BASH worden aangeroepen, en dat is toevallig een REPL met blijvende state (het bestandssysteem), dus werkt CLI veel beter dan MCP. Maar dit is op geen stukken na een intrinsieke eigenschap van MCP. Het betekent alleen dat we betere harnesses nodig hebben.

Dat klopt, en het verklaart een observatie die veel mensen hebben gedaan zonder haar te verklaren. Een agent die een CLI krijgt, schrijft een bestand, leest het drie stappen later terug, grept erin, pipet het. Een agent die een kale MCP-server krijgt, krijgt één resultaat per aanroep en moet al het andere in zijn contextvenster meedragen. De CLI won niet op protocol. Hij won omdat bash een plek meebracht om dingen neer te zetten.

Zijn weddenschap volgt daaruit. Iemand bouwt een inbedbare engine in de stijl van SQLite die bash, TypeScript of tool calls omzet in één plan, gecontroleerd voordat het draait en gebouwd voor durable execution. dquickts is een stap in die richting. [De README](https://github.com/stencil-hq/dquickts) beschrijft een REPL “waarvan de functies en variabelen herstarts overleven”. Ook beschrijft ze een duurzame host call: “een script roept een hostfunctie aan waarvan het antwoord uren later komt. Het proces slaat het script op en stopt.”

Tot hier volledige instemming. Nu de versmalling.

## Welke state, voor welke taak

“State bewaren” verbergt een vraag: wiens state, en in welke vorm? Voor een coding agent is de state de repository en de shell, en een REPL die herstarts overleeft is een goed antwoord. Voor een agent die een sync bouwt tussen twee SaaS-systemen is de state die hij tussen stappen nodig heeft al bepaald door de taak. Vier records dragen haar, en geen van hen is een variabele in een script.

**De cursor.** Een sync leest een bron in vensters en moet weten waar het vorige venster eindigde. In de sync-engine van Tajo is dat een [opgeslagen cursor](https://tajo.io/nl/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#cursor) per sync en scope, geschreven na elke batch zonder mislukte records, en de volgende run gaat vanaf daar verder. Een geplande run slaat zijn startpunt op voordat hij iets anders doet, zodat een eerste run die mislukt de volgende vanaf hetzelfde punt laat lezen. Het principe hierachter, stuur de delta en nooit de tabel, staat in de [uitleg over reverse ETL](https://tajo.io/nl/blog/what-is-reverse-etl/). Ik laat de cursor daar.

**Het openstaande voorstel.** Op de connector-MCP-tools van Tajo bestaat een schrijfactie uit twee aanroepen. `brevo_propose` valideert de invoer tegen de gedeclareerde actie en legt een voorstel vast. Er wordt niets verzonden. Het resultaat bevat een voorstel-id en, wanneer een mens moet beslissen, de pagina waar die het goedkeurt. `brevo_apply` voert de goedgekeurde schrijfacties van die verbinding op volgorde uit en stopt bij de eerste die nog op een mens wacht. Het record daartussen is de state. Het bevat de invoer en een digest ervan, zodat een goedgekeurde schrijfactie niet kan worden gewijzigd tussen de beslissing en de apply. Het bevat niet de antwoordwaarden van de provider, want een append-only goedkeuringsrecord mag geen opslag van persoonsgegevens worden.

**De lease.** Een run van een sync houdt gedurende zijn hele levensduur één [exclusieve lease](https://tajo.io/nl/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#lease) vast, met een eigenaarstoken, een tijdslimiet van standaard vijftien minuten, en verlenging. Een verouderde eigenaar kan een nieuwere lease niet vrijgeven. Als de lease-opslag onbereikbaar is, weigert de run te starten in plaats van te gokken. Welke worker de run heeft, is een feit in de lease, niet in het geheugen van een proces.

**Het plan.** `syncs.dryRun` leest het volgende venster vanaf de cursor, bepaalt per record invoegen, bijwerken, verwijderen of ongewijzigd, en schrijft precies één rij: het plan, dat een uur wordt bewaard. Het verplaatst geen cursor en schrijft niets naar een van beide systemen. `syncs.apply` schrijft dat plan één keer. Het weigert als de cursor is verplaatst, of als intussen meer dan een getolereerd aandeel van de records is veranderd. Een herhaling geeft het eerste resultaat terug in plaats van opnieuw te schrijven.

Dat is de state tussen stappen: vier records met id’s, leesbaar voor elke agent die ze mag zien.

## Waarom de MCP-kant niets in een sessie bewaart

Zodra de state in de records zit, heeft een sessie niets meer vast te houden. Dus houden de MCP-endpoints van Tajo er geen vast. Elk verzoek krijgt een nieuwe server en een transport zonder sessie-id. Leesacties zijn één verzoek per aanroep, met begrensde resultaten zodat één antwoord een beurt niet kan overspoelen. Schrijfacties zijn eerst voorstellen, dan toepassen, en het id van het voorstel is wat je meeneemt.

Het protocol bewoog dezelfde kant op. De [revisie 2026-07-28 van de MCP-specificatie](https://modelcontextprotocol.io/specification/2026-07-28/changelog) verwijderde sessies op protocolniveau en de header `Mcp-Session-Id` uit het Streamable HTTP-transport. Een server die continuïteit nodig heeft, maakt nu zijn eigen handle aan en krijgt die terug als gewoon argument. Dat is precies wat een voorstel-id is. [Wat is een MCP-server](https://tajo.io/nl/blog/what-is-an-mcp-server/) behandelt die revisie en hoe een tool call verschilt van een duurzame sync; dit stuk gaat alleen over waar de state terechtkomt.

Het gevolg is het gevolg dat telt voor integratiewerk. Stel dat een agent in Claude Code een schrijfactie voorstelt en dat zijn sessie eindigt. De mens keurt het een uur later goed. Een agent in een andere client, of dezelfde morgen, roept apply aan met het id en de schrijfactie komt aan, één keer. Een gecrashte agent verliest zijn contextvenster en verder niets. Er werd niets gepauzeerd, dus hoefde er niets te worden hervat.

## Wat we niet hebben, en wat dat kost

Het eerlijke bezwaar komt van de REPL-kant en gaat zo: “Prima, je records overleven. Maar elke aanroep leest opnieuw wat de vorige aanroep al had. Een script zou de contacten één keer ophalen, ze joinen, filteren en een getal teruggeven. Jouw agent doet vijf aanroepen en betaalt voor elke.” Dat bezwaar klopt wat het mechanisme betreft, en we hebben de prijs niet gemeten. De evaluatie die aanroepen per taak zou tellen, met en zonder meegedragen state, is niet gebouwd. Tot die er is, blijft de bewering dat call mode meer tokens kost onbeantwoord.

Nog drie dingen moeten ronduit worden gezegd, omdat een lezer die dit artikel uitleest ze zich niet moet inbeelden:

- **Er draait vandaag geen door klanten geschreven code.** De leesacties van de connectors zijn één verzoek per aanroep, zonder sandbox in het pad. In onze stack bestaat een code runner, en dat is een interne tool. In productie staat hij uit tenzij twee aparte flags zijn gezet, en hij antwoordt alleen aan beheerders. Elke aanroep krijgt een nieuwe context, bindings die alleen lezen toestaan, en een budget van twee seconden voor synchrone code. Zijn eigen header zegt in hoofdletters dat hij een correctheidsgrens is, geen beveiligingsgrens.
- **Geen REPL.** Niets wat een agent in één aanroep berekent, is beschikbaar voor de volgende, tenzij het als record met een id is geschreven.
- **Geen blijvende agentsessie.** De client bewaart zijn gesprek. Tajo bewaart de voorstellen, plannen, cursors en leases. Er is geen derde ding.

## Waar een duurzame engine voor is

Niets hiervan is een argument tegen dquickts. De README somt op waar snapshots voor zijn, en de lijst is goed. Een inactief script laten overwinteren en zijn geheugen vrijgeven. Een lange berekening in stukken draaien op welke worker er ook vrij is. Elke instantie warm starten vanaf een snapshot in plaats van de setup te herhalen. Een script laten wachten op een host call waarvan het antwoord later komt. Voor de werkruimte van een coding agent, of een notebook dat een herstart moet overleven, is dat het juiste gereedschap.

Een sync zou er toch niet van moeten afhangen, om drie redenen die de README me zelf geeft. Ten eerste: “Een sessiebestand laadt alleen in de qjs-build die het schreef.” Een heap die door één engineversie is geschreven, is na een upgrade een dood bestand; een cursor is een string. Ten tweede: “Snapshots zijn vertrouwde invoer, net als bytecode,” en “kwaadaardige worden niet gedetecteerd.” Een opgeslagen heap is iets wat je moet beschermen, niet iets wat je kunt reviewen; een voorstel is een rij die een mens leest voordat hij het goedkeurt. Ten derde bevat een snapshot alles wat het script had gelezen, klantrecords inbegrepen. De records hierboven zijn andersom gebouwd: het plan slaat digests op, geen waarden, en het voorstelrecord slaat nooit het antwoord van de provider op.

Een van de regels waarmee we bouwen is dat deterministisch wint van slim. Een heap die midden in een lus hervat, is slim. Een cursor, een voorstel, een lease en een plan zijn saai, inspecteerbaar en hetzelfde op elke engine, in elke client, na elke crash.

Lütke stelde de juiste vraag. Waar woont de state? Voor een sync zou het antwoord nooit in het proces liggen. Het zou altijd in de records liggen, omdat daar de mens die de schrijfactie goedkeurt haar moet kunnen lezen.

## Veelgestelde vragen

**Is MCP of een CLI beter voor AI-agents?**

Geen van beide, op zichzelf. Het punt dat Tobi Lütke op 21 september 2026 maakte klopt: beide werken zodra de aanroepen lopen via iets dat state tussen stappen bewaart. Een CLI voelt vandaag beter omdat bash state bewaart in het bestandssysteem. Het verschil zit niet in het protocol, maar in de harness.

**Welke state heeft een agent nodig tussen de stappen van een sync?**

De eigen records van de integratie: een cursor die zegt waar de vorige run stopte, een voorstel dat wacht op goedkeuring door een mens, een lease die zegt welke worker de run heeft, en een dry-runplan waaraan de apply moet voldoen. Niets daarvan is een variabele in een script. Het zijn rijen met id's.

**Waarom bewaart de MCP-server van Tajo niets in een sessie?**

Elk verzoek krijgt een nieuwe server en een transport zonder sessie-id. Wat de volgende aanroep nodig heeft, is een record: een voorstel-id, een plan-id, een cursor. Zo kan een tweede agent, in een andere client, verdergaan waar de eerste stopte, en verliest een gecrashte agent niets behalve zijn eigen contextvenster.

**Voert Tajo code uit die door agents is geschreven?**

Niet voor klanten. Leesacties op de connectortools zijn één verzoek per aanroep, zonder sandbox. De code runner die bestaat is een interne tool achter twee flags, alleen bereikbaar voor beheerders, met alleen-lezen bindings en een nieuwe context per aanroep. Hij zegt over zichzelf dat hij geen beveiligingsgrens is. Er is geen REPL en geen blijvende sessie.

**Waar is een duurzame JavaScript-engine zoals dquickts goed voor?**

De README noemt de toepassingen: een inactief script laten overwinteren, een lange berekening in stukken over workers verdelen, warm starten vanaf een snapshot, en een script laten wachten op een host call waarvan het antwoord uren later komt. Allemaal echt. Een sync zou er toch niet van moeten afhangen: een snapshot laadt alleen in de engine-build die hem schreef, is ondoorzichtig voor een reviewer en bevat alle klantgegevens die het script heeft gelezen.

## Gerelateerde artikelen

### [Het antwoord klopt. De route niet.](https://tajo.io/nl/blog/the-answer-is-right-the-path-is-wrong/)

8 okt 2026 · 4 min leestijd

3 secties

### [Wat je bij elke MCP-toolaanroep moet loggen](https://tajo.io/nl/blog/what-to-log-for-every-mcp-tool-call/)

6 nov 2026 · 8 min leestijd

6 secties

### [Wat is een MCP-server? Zo werken MCP en MCP UI in 2026](https://tajo.io/nl/blog/what-is-an-mcp-server/)

3 okt 2026 · 15 min leestijd

Stap 1 → Stap 6
