---
title: "MCP o CLI è la domanda sbagliata. Chiediti dove vive lo stato."
description: "Tobi Lütke dice che il confronto tra MCP e CLI si gioca al livello sbagliato: entrambi funzionano non appena lo stato persiste tra un passaggio e l’altro. D’accordo. Per una sincronizzazione, però, quello stato sono i record dell’integrazione stessa, non l’heap di un REPL, ed è per questo che gli strumenti MCP di Tajo non tengono nulla in una sessione."
url: https://tajo.io/it/blog/mcp-or-cli-is-the-wrong-question/
markdown: https://tajo.io/it/blog/mcp-or-cli-is-the-wrong-question.md
lang: it-IT
author: "Set Noa"
author_url: https://tajo.io/it/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, 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, nl-NL: /nl/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}
---

[Agenti di IA](https://tajo.io/it/blog/?category=ai-agents)

# MCP o CLI è la domanda sbagliata. Chiediti dove vive lo stato.

Tobi Lütke dice che il confronto tra MCP e CLI si gioca al livello sbagliato: entrambi funzionano non appena lo stato persiste tra un passaggio e l’altro. D’accordo. Per una sincronizzazione, però, quello stato sono i record dell’integrazione stessa, non l’heap di un REPL, ed è per questo che gli strumenti MCP di Tajo non tengono nulla in una sessione.

mcp vs cli

Un agente IA dovrebbe usare MCP o una CLI?

Lütke ha ragione: MCP contro CLI è il livello sbagliato. Entrambi funzionano non appena lo stato persiste tra i passaggi, e bash vince solo perché il file system è quello stato. Per una sincronizzazione, però, lo stato non è l’heap di un REPL. Sono i record propri dell’integrazione: un cursore, una proposta in sospeso, un lease, un piano. Per questo gli strumenti MCP di Tajo non tengono nulla in una sessione: qualsiasi agente, da qualsiasi client, riprende dai record. Oggi Tajo non esegue codice dei clienti e non ha un REPL, e lo diciamo chiaramente più sotto.

[Scopri di più](https://tajo.io/it/blog/mcp-or-cli-is-the-wrong-question/#il-livello-di-cui-parla)

Il 21 settembre 2026, alle 00:33 UTC, Tobi Lütke ha pubblicato questo: «MCP contro CLI per gli LLM è la discussione sbagliata. Si sta discutendo al livello sbagliato.» Poi, [il 23 ottobre](https://x.com/tobi/status/2113596638899974567), ha segnalato un nuovo repository chiamato [dquickts](https://github.com/stencil-hq/dquickts). È un fork di QuickJS che può mettere in pausa un programma nel mezzo di un ciclo `for`, scrivere tutto il suo heap in byte e riprenderlo in un altro processo. «Ottimo ambiente per il code mode», ha scritto.

Sul livello ha ragione. Voglio prima dargli ragione, con le sue parole, e poi restringere l’affermazione finché non dice qualcosa che una sincronizzazione possa usare.

## Il livello di cui parla

Ecco l’argomento come l’ha formulato lui, che è l’unico modo onesto di citare un argomento che intendi piegare:

> Entrambi funzionano incredibilmente bene, purché passino attraverso un ambiente simile a un REPL che possa rendere persistente lo stato. La cosa buffa della CLI è che agli strumenti CLI di solito si accede tramite BASH, che guarda caso è un REPL con stato persistente (il file system), quindi la CLI funziona molto meglio di MCP. Ma questa non è neanche lontanamente una proprietà intrinseca di MCP. Significa solo che ci servono harness migliori.

È corretto, e spiega un’osservazione che molti hanno fatto senza spiegarla. Un agente con una CLI scrive un file, lo rilegge tre passaggi dopo, ci passa sopra grep, lo manda in una pipe. Un agente con un semplice server MCP riceve un risultato per chiamata e deve portarsi tutto il resto nella finestra di contesto. La CLI non ha vinto per il protocollo. Ha vinto perché bash arrivava con un posto dove mettere le cose.

La sua scommessa ne discende. Qualcuno costruisce un motore incorporabile, sul modello di SQLite, che trasforma bash, TypeScript o chiamate agli strumenti in un unico piano, verificato prima di essere eseguito e progettato per l’esecuzione durevole. dquickts è un passo in quella direzione. [Il suo README](https://github.com/stencil-hq/dquickts) descrive un REPL «le cui funzioni e variabili sopravvivono ai riavvii». Descrive anche una chiamata all’host durevole: «uno script chiama una funzione dell’host la cui risposta arriva ore dopo. Il processo salva lo script ed esce.»

Fin qui, pieno accordo. Ora il restringimento.

## Quale stato, per quale lavoro

“Rendere persistente lo stato” nasconde una domanda: lo stato di chi, e in che forma? Per un agente di programmazione lo stato è il repository e la shell, e un REPL che sopravvive ai riavvii è una buona risposta. Per un agente che costruisce una sincronizzazione tra due sistemi SaaS, lo stato di cui ha bisogno tra i passaggi è già definito dal lavoro. Lo portano quattro record, e nessuno di essi è una variabile in uno script.

**Il cursore.** Una sincronizzazione legge una sorgente a finestre e deve sapere dove è finita l’ultima finestra. Nel motore di sincronizzazione di Tajo è un [cursore salvato](https://tajo.io/it/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#cursor) per ogni sincronizzazione e ambito, scritto dopo ogni batch senza record falliti, e l’esecuzione successiva riparte da lì. Un’esecuzione pianificata salva il proprio punto di partenza prima di fare qualsiasi altra cosa, così se una prima esecuzione fallisce, la successiva legge dallo stesso punto. Il principio alla base, inviare il delta e mai la tabella, è nella [guida al reverse ETL](https://tajo.io/it/blog/what-is-reverse-etl/). Per il cursore mi fermo qui.

**La proposta in sospeso.** Negli strumenti MCP dei connettori di Tajo, una scrittura è fatta di due chiamate. `brevo_propose` convalida l’input rispetto all’azione dichiarata e registra una proposta. Non viene inviato nulla. Il risultato contiene un id di proposta e, quando una persona deve decidere, la pagina in cui la approva. `brevo_apply` esegue in ordine le scritture approvate di quella connessione e si ferma alla prima che sta ancora aspettando una persona. Il record nel mezzo è lo stato. Contiene l’input e un suo digest, così una scrittura approvata non può essere modificata tra la decisione e l’apply. Non contiene i valori di risposta del provider, perché un registro di approvazioni append-only non deve diventare un archivio di dati personali.

**Il lease.** Un’esecuzione di una sincronizzazione detiene un [lease esclusivo](https://tajo.io/it/resources/help/troubleshooting/recover-a-stuck-or-failed-sync/#lease) per tutta la sua durata, con un token del proprietario, un limite di tempo di quindici minuti per impostazione predefinita e il rinnovo. Un proprietario scaduto non può rilasciare un lease più recente. Se l’archivio dei lease non è raggiungibile, l’esecuzione si rifiuta di partire invece di tirare a indovinare. Quale worker detiene l’esecuzione è un fatto scritto nel lease, non nella memoria di un processo.

**Il piano.** `syncs.dryRun` legge la finestra successiva a partire dal cursore, stabilisce per ogni record se inserire, aggiornare, eliminare o lasciare invariato e scrive esattamente una riga: il piano, conservato per un’ora. Non sposta nessun cursore e non scrive nulla in nessuno dei due sistemi. `syncs.apply` scrive quel piano una volta. Si rifiuta se il cursore si è spostato, o se nel frattempo è cambiata più di una quota tollerata dei record. Una ripetizione restituisce il primo risultato invece di scrivere di nuovo.

Questo è lo stato tra i passaggi: quattro record con un id, leggibili da qualsiasi agente autorizzato a vederli.

## Perché il lato MCP non tiene nulla in una sessione

Una volta che lo stato sta nei record, a una sessione non resta nulla da tenere. Quindi gli endpoint MCP di Tajo non ne tengono. Ogni richiesta riceve un server nuovo e un trasporto senza id di sessione. Le letture sono una richiesta per chiamata, con risultati limitati perché una singola risposta non possa inondare un turno. Le scritture sono prima propose, poi apply, e quello che ti porti dietro è l’id della proposta.

Il protocollo si è mosso nella stessa direzione. La [revisione 2026-07-28 della specifica MCP](https://modelcontextprotocol.io/specification/2026-07-28/changelog) ha rimosso le sessioni a livello di protocollo e l’header `Mcp-Session-Id` dal trasporto Streamable HTTP. Un server che ha bisogno di continuità ora genera il proprio handle e lo riceve indietro come un normale argomento. È esattamente ciò che è un id di proposta. [Che cos’è un server MCP](https://tajo.io/it/blog/what-is-an-mcp-server/) tratta quella revisione e la differenza tra una chiamata a uno strumento e una sincronizzazione durevole; questo articolo riguarda solo dove va lo stato.

La conseguenza è quella che conta per il lavoro di integrazione. Supponi che un agente in Claude Code proponga una scrittura e che la sua sessione finisca. La persona la approva un’ora dopo. Un agente in un altro client, o lo stesso domani, chiama apply con l’id e la scrittura va a segno, una volta sola. Un agente che va in crash perde la sua finestra di contesto e nient’altro. Non è stato sospeso nulla, quindi non c’era nulla da riprendere.

## Cosa non abbiamo, e quanto costa

L’obiezione onesta arriva dal lato REPL e suona così: “Va bene, i tuoi record sopravvivono. Ma ogni chiamata rilegge ciò che la chiamata precedente aveva già. Uno script recupererebbe i contatti una volta, li unirebbe, li filtrerebbe e restituirebbe un numero. Il tuo agente fa cinque chiamate e paga per ciascuna.” L’obiezione ha ragione sul meccanismo, e il prezzo non l’abbiamo misurato. La valutazione che conterebbe le chiamate per attività con e senza stato mantenuto non è stata costruita. Finché non lo sarà, l’affermazione che la modalità a chiamate costi più token resta senza risposta.

Altre tre cose vanno dette chiaramente, perché chi finisce di leggere questo articolo non deve immaginarsele:

- **Oggi non gira codice scritto dai clienti.** Le letture dei connettori sono una richiesta per chiamata, senza sandbox nel percorso. Nel nostro stack esiste un code runner, ed è uno strumento interno. In produzione è disattivato a meno che non siano impostati due flag distinti, e risponde solo agli amministratori. Ogni chiamata riceve un contesto nuovo, binding che consentono solo letture e un budget di due secondi per il codice sincrono. La sua stessa intestazione dice in maiuscolo che è un confine di correttezza, non di sicurezza.
- **Nessun REPL.** Niente di ciò che un agente calcola in una chiamata è disponibile per la successiva, a meno che non sia stato scritto come record con un id.
- **Nessuna sessione persistente dell’agente.** Il client conserva la sua conversazione. Tajo conserva le proposte, i piani, i cursori e i lease. Non c’è una terza cosa.

## A cosa serve un motore durevole

Niente di tutto questo è un argomento contro dquickts. Il suo README elenca a cosa servono gli snapshot, e l’elenco è valido. Ibernare uno script inattivo e liberarne la memoria. Eseguire un calcolo lungo a fette sul worker che è libero. Avviare a caldo ogni istanza da uno snapshot invece di ripetere la configurazione. Parcheggiare uno script su una chiamata all’host la cui risposta arriva più tardi. Per lo spazio di lavoro di un agente di programmazione, o per un notebook che deve sopravvivere a un riavvio, è lo strumento giusto.

Una sincronizzazione però non dovrebbe dipenderne, per tre ragioni che mi dà lo stesso README. Primo: «Un file di sessione si carica solo nella build di qjs che l’ha scritto.» Un heap scritto da una versione del motore è un file morto dopo un aggiornamento; un cursore è una stringa. Secondo: «Gli snapshot sono input fidati, come il bytecode», e «quelli malevoli non vengono rilevati». Un heap salvato è qualcosa che devi proteggere, non qualcosa che puoi revisionare; una proposta è una riga che una persona legge prima di approvarla. Terzo, uno snapshot contiene tutto ciò che lo script aveva letto, record dei clienti compresi. I record descritti sopra sono costruiti al contrario: il piano salva digest, non valori, e il record della proposta non salva mai la risposta del provider.

Una delle regole con cui costruiamo è che il deterministico batte l’ingegnoso. Un heap che riprende a metà di un ciclo è ingegnoso. Un cursore, una proposta, un lease e un piano sono noiosi, ispezionabili e uguali su ogni motore, in ogni client, dopo ogni crash.

Lütke ha posto la domanda giusta. Dove vive lo stato? Per una sincronizzazione, la risposta non sarebbe mai stata nel processo. Sarebbe sempre stata nei record, perché è lì che la persona che approva la scrittura deve poterla leggere.

## Domande frequenti

**Per gli agenti IA è meglio MCP o una CLI?**

Nessuno dei due, di per sé. Il punto di Tobi Lütke del 21 settembre 2026 regge: entrambi funzionano non appena le chiamate passano attraverso qualcosa che mantiene lo stato tra i passaggi. Oggi una CLI sembra migliore perché bash mantiene lo stato nel file system. La differenza non è il protocollo, è l’harness.

**Di quale stato ha bisogno un agente tra i passaggi di una sincronizzazione?**

Dei record propri dell’integrazione: un cursore che dice dove si è fermata l’ultima esecuzione, una proposta in attesa dell’approvazione di una persona, un lease che dice quale worker detiene l’esecuzione e un piano di dry run a cui l’apply deve corrispondere. Niente di tutto questo è una variabile in uno script. Sono righe con un id.

**Perché il server MCP di Tajo non tiene nulla in una sessione?**

Ogni richiesta riceve un server nuovo e un trasporto senza id di sessione. Qualunque cosa serva alla chiamata successiva è un record: un id di proposta, un id di piano, un cursore. Così un secondo agente, in un client diverso, può riprendere da dove si è fermato il primo, e un agente che va in crash perde soltanto la propria finestra di contesto.

**Tajo esegue codice scritto dagli agenti?**

Non per i clienti. Le letture sugli strumenti dei connettori sono una richiesta per chiamata, senza sandbox. Il code runner che esiste è uno strumento interno dietro due flag, raggiungibile solo dagli amministratori, con binding di sola lettura e un contesto nuovo a ogni chiamata. Dice di sé stesso di non essere un confine di sicurezza. Non c’è nessun REPL e nessuna sessione persistente.

**A cosa serve un motore JavaScript durevole come dquickts?**

Il suo README ne elenca gli usi: ibernare uno script inattivo, suddividere un calcolo lungo tra più worker, fare un avvio a caldo da uno snapshot e parcheggiare uno script su una chiamata all’host la cui risposta arriva ore dopo. Tutto reale. Una sincronizzazione però non dovrebbe dipenderne: uno snapshot si carica solo nella build del motore che l’ha scritto, è opaco per chi lo revisiona e contiene tutti i dati dei clienti che lo script ha letto.

## Articoli correlati

### [La risposta è giusta. Il percorso è sbagliato.](https://tajo.io/it/blog/the-answer-is-right-the-path-is-wrong/)

8 ott 2026 · 4 min di lettura

3 sezioni

### [Cosa registrare per ogni chiamata a uno strumento MCP](https://tajo.io/it/blog/what-to-log-for-every-mcp-tool-call/)

6 nov 2026 · 9 min di lettura

6 sezioni

### [Cos’è un server MCP? Come funzionano MCP e MCP UI nel 2026](https://tajo.io/it/blog/what-is-an-mcp-server/)

3 ott 2026 · 16 min di lettura

Passo 1 → Passo 6
