Dalla sua introduzione a fine 2024 il Model Context Protocol ha avuto un’adozione molto rapida e continua. Come naturale evoluzione, e per rispondere alle tante richieste arrivate nel tempo, è stata rilasciata la versione 2026-07-28, che introduce grandi cambiamenti.
Nei precedenti articoli della serie abbiamo visto cos’è MCP e come costruire un server in .NET. Qui vediamo cosa è cambiato e perché.
Il cambio da stateful a stateless
Il più grande cambiamento è il passaggio da stateful a stateless. Tutti gli altri cambiamenti, grandi e piccoli, discendono da lì.
Quando il protocollo è nato, il suo scopo era principalmente quello di collegare processi locali attraverso una pipe, dove client e server possono scriversi quando vogliono. Quella simmetria su HTTP non esiste: parla il client, risponde il server. Per rendere comunque possibile la comunicazione server → client, erano stati introdotti dei meccanismi appositi: una sessione tenuta aperta, identificata da un Mcp-Session-Id, su cui il server poteva scrivere quando ne aveva bisogno.
Questo ha due costi. Il primo è che bisogna mantenere lo stato. Il secondo, più fastidioso, è che lega la richiesta del client all’istanza esatta del server che ha aperto quella sessione, e questo non permette di scalare: serve che il load balancer mandi sempre lo stesso client alla stessa istanza, e se quella cade, la sessione muore con lei.
La nuova versione interrompe questo flusso per sposarsi al meglio con HTTP e soprattutto per garantire scalabilità e affidabilità.
MRTR: Multi Round-Trip Requests
Se il server ha bisogno di informazioni dal client, semplicemente risponde: nella risposta chiede quello che gli serve e chiude la comunicazione. Il client, una volta ottenute le informazioni necessarie, richiama il server con la stessa chiamata arricchita della risposta.
Facciamo un esempio concreto. Il tool è close_ticket: chiude un ticket di assistenza, ma prima vuole farsi confermare il motivo. È il caso minimo che richiede un’interazione a metà chiamata, ed è esattamente il punto in cui le due revisioni divergono.
Nel round 1 il client chiama tools/call. Il server non ha il motivo di chiusura, quindi non arriva in fondo al lavoro: risponde con resultType: input_required, la domanda da girare all’utente e un requestState. La chiamata HTTP finisce qui: il server lascia andare la connessione, l’handler termina, in memoria non resta niente. Il requestState è la sola continuità fra i due round, lo produce il server e il client lo rimanda indietro così com’è.
Nel round 2 il client ripete la stessa tools/call, aggiungendo il requestState ricevuto e la risposta dell’utente dentro inputResponses. Questa volta il server ha tutto quello che gli serve e chiude con resultType: complete.
Il campo da guardare è proprio resultType: input_required significa “mi manca qualcosa, richiamami”, complete che il giro è finito.
La differenza fra i due mondi si legge a colpo d’occhio nelle tracce dello stesso identico tool, eseguito prima con una sessione e poi in MRTR.

Con la sessione: 26 span, profondità 8. Si vedono l’initialize, l’elicitation/create annidata dentro il tools/call e il DELETE finale che smonta la sessione. In mezzo c’è un GET / che da solo dura 0,15s su una traccia di 0,37s: è il canale server → client, aperto per tutta la sessione.

Con MRTR: 19 span, profondità 4. I due tools/call sono fratelli, non uno dentro l’altro. Nessuna barra GET, nessun DELETE.
La differenza di profondità è il cambio di filosofia reso visibile: prima una chiamata conteneva una conversazione, ora sono due chiamate indipendenti.
Addio handshake
Sempre nell’ottica dello stateless, è stato rimosso anche il meccanismo di inizializzazione: l’exchange initialize/notifications/initialized e l’Mcp-Session-Id lasciano il posto a richieste standalone.
L’handshake faceva due lavori, e i due lavori hanno preso strade opposte.
La parte client — versione del protocollo, capability e identità — serviva al server per sapere con chi stava parlando. Ora viaggia in ogni richiesta dentro _meta, sotto tre chiavi:
io.modelcontextprotocol/protocolVersion
io.modelcontextprotocol/clientCapabilities
io.modelcontextprotocol/clientInfo
Sono, letteralmente, il contenuto dell’initialize spalmato su ogni richiesta. E non sono facoltative: se ne manca una, il server rifiuta la richiesta.
{"error":{"code":-32602,"message":
"Requests using protocol version '2026-07-28' must include
'_meta/io.modelcontextprotocol/protocolVersion'."}}
La parte server — serverInfo e serverCapabilities — permetteva al client di sapere che cosa era in grado di fare il server. Ora è diventata una richiesta ordinaria, server/discover, ed è facoltativa: un client che sa già quale tool vuole chiamare può saltarla e andare dritto al tools/call.
Si passa quindi da una negoziazione, con due parti che si mettono d’accordo e ricordano l’accordo, a una dichiarazione ripetuta a ogni richiesta.
👉 Un trucco pratico: con l'SDK .NET, una GET sull'endpoint di un server stateless risponde 405 Method Not Allowed, perché in quella modalità i verbi GET e DELETE non vengono nemmeno registrati. Non è garantito dalla specifica, ma è un modo rapido per capire da fuori come sta girando un server.
Le altre modifiche
- Routing basato su header. Sono stati introdotti
Mcp-MethodeMcp-Name. Dato che è tutto basato su POST verso lo stesso endpoint, un proxy che avesse voluto instradare le richieste avrebbe dovuto leggere e analizzare il body JSON: ora gli bastano due header. - Liste cacheabili. Le risposte di
tools/list,prompts/list,resources/listeresources/readportano orattlMsecacheScope: il client sa per quanto tenerle e con che ambito. Senza una sessione da cui dipendere, la cache diventa possibile. - Roots e Sampling deprecate. Erano richieste che partivano dal server verso il client. Continuano a funzionare per almeno dodici mesi, ma le nuove implementazioni non dovrebbero adottarle.
- Logging deprecato. Era una notifica e non una domanda in attesa di risposta, quindi non potrebbe nemmeno avere un veicolo MRTR. La soluzione diventa, giustamente, OpenTelemetry — e se vuoi approfondire, c’è la serie dedicata su questo blog.
- Elicitation viva. Non è deprecata: cambia solo il veicolo, e diventa il caso d’uso principale di MRTR.
- Autorizzazione. Anche qui ci sono novità, ma sono abbastanza corpose da meritare un articolo a parte.
Pro, contro e a cosa fare attenzione
Come in tutte le cose, ci sono pro e contro, e punti su cui bisogna prestare attenzione.
A fronte di una maggiore scalabilità del sistema, abbiamo più round-trip, quindi più latenza: un’interazione che prima stava dentro una chiamata ora ne richiede due. Su una rete locale si parla di millisecondi, su una connessione lenta di molto di più.
E soprattutto dobbiamo prestare attenzione a come scriviamo il server, perché da ora in avanti va gestita l’idempotenza.
Conclusioni
MCP è passato da “il server può richiamare il client nel mezzo di una chiamata” a “il server risponde e si dimentica di te”. La fine delle sessioni, gli header nuovi, le liste cacheabili e le feature deprecate discendono tutti da questa scelta.
Il prezzo è qualche round-trip in più e la responsabilità dell’idempotenza. Quello che si ottiene è un server che si mette dietro un load balancer qualsiasi senza configurazioni particolari, e che può essere riavviato senza effetti drastici sulle conversazioni in corso.
Nel prossimo articolo vediamo la pratica: come scrivere in .NET un server MCP con la nuova revisione e come migrare un server esistente senza rompere i client vecchi.