MCP spiegato a partire dalla specifica: che cos’è davvero un server
Una riga di JSON in un sottoprocesso e tornano tredici definizioni di tool, lette contro la revisione 2026-07-28 che ha rimosso l’handshake.
In questa pagina
Installa un server MCP pubblicato, inviagli una riga di JSON e leggi cosa torna indietro.
npm i @modelcontextprotocol/server-everything@2026.8.31
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
| npx @modelcontextprotocol/server-everything stdio{"result":{"tools":[{"name":"echo","title":"Echo Tool","description":"Echoes
back the input string","inputSchema":{"$schema":"http://json-schema.org/draft-07/
schema#","type":"object","properties":{"message":{"type":"string","description":
"Message to echo"}},"required":["message"]},"annotations":{"readOnlyHint":true,
… … 7,663 bytes on one line …
"jsonrpc":"2.0","id":1}Tredici definizioni di tool, su una sola riga, da un processo che ha letto una riga dal proprio standard input. Hai appena parlato il Model Context Protocol, senza SDK, senza libreria client e senza framework. Questo è tutto: un transport, un formato di messaggi e un piccolo insieme di metodi nominati.
Il Capitolo 18 ha definito un tool come due cose: un JSON Schema che il modello vede, e un endpoint nel tuo codice che il modello non vede mai. Il Capitolo 23 ha costruito un harness che ne contiene un catalogo. Nessuno dei due ha risposto alla domanda che decide se tutto questo sia riutilizzabile: chi scrive lo schema, e come arriva da chi lo ha scritto dentro il tuo prompt? MCP è una risposta a questa domanda, e vale la pena leggerla nell’originale, perché quasi tutto ciò che è stato scritto a riguardo descrive una revisione che non esiste più.
Tre cose del comando che hai appena eseguito sono sbagliate, e ciascuna è una sezione di questo capitolo. Non portava nessuna versione del protocollo, quindi un server conforme lo avrebbe rifiutato. Ha ottenuto comunque una risposta, per una ragione che la specifica chiama un rischio più che una feature. E ha chiesto una delle tre primitive senza mai scoprire che le altre due esistono.
Il problema che risolve, e l’analogia che la specifica fa da sé
Link alla sezione: Il problema che risolve, e l’analogia che la specifica fa da séPrima del wire, l’aritmetica. Hai applicazioni AI e cose che dovrebbero poter raggiungere: un calendario, un tracker di ticket, un database di warehouse, uno strumento di design. Senza un contratto condiviso, qualcuno scrive integrazioni, e ognuna è uno schema più un endpoint più una storia di autenticazione più un onere di manutenzione. Con un contratto, il vendor del tool scrive un server, il vendor dell’applicazione scrive un client, e il totale è .
Non è un’osservazione nuova, e la specifica dice da chi arriva l’idea:
MCP takes some inspiration from the Language Server Protocol, which standardizes how to add support for programming languages across a whole ecosystem of development tools. In a similar way, MCP standardizes how to integrate additional context and tools into the ecosystem of AI applications.1
Prendi questo confronto alla lettera, non come un complimento. Prima di quel protocollo, supportare un linguaggio in un editor significava un plugin per editor; dopo, il team di un linguaggio spediva un solo server e ogni editor lo otteneva. La misura del successo non era l’eleganza, era il fatto che il numero di integrazioni smetteva di moltiplicarsi. Qui segue la stessa cosa: il valore è nel numero di implementazioni, non nel design. Un protocollo che due prodotti parlano è un formato dati con cerimonia extra.
Cosa c’è davvero sul wire
Link alla sezione: Cosa c’è davvero sul wireI messaggi MCP sono JSON-RPC 2.0. Una richiesta è un oggetto con jsonrpc, un id, un method e params opzionali; una risposta porta lo stesso id e o result o error; una notifica è una richiesta senza id e non riceve risposta. La specifica aggiunge tre vincoli: l’id deve essere una stringa o un numero e non deve essere null, non deve collidere con un’altra richiesta ancora in volo, e ogni risultato deve portare un campo resultType.2
Sul transport stdio — quello usato dal comando sopra — la regola di framing è una riga per messaggio:
Messages are delimited by newlines, and MUST NOT contain embedded newlines. […] The server MUST NOT write anything to its
stdoutthat is not a valid MCP message.3
Quest’ultima clausola è il modo più comune in cui un server fatto in casa si rompe, e si rompe in silenzio: un console.log fuori posto, una progress bar, un warning di deprecazione da una dependency, e il parser a righe del client incontra qualcosa che non è JSON. La via di fuga è nella stessa sezione: il server può scrivere qualunque cosa voglia su stderr, e il client non dovrebbe trattarlo come errore. Il reference server sopra stampa Starting default (STDIO) server... a ogni avvio, su stderr, ed è per questo che la pipe ha comunque funzionato.
L’altro transport standard è Streamable HTTP: ogni messaggio è un POST a un singolo endpoint, e la risposta è o un oggetto JSON o uno stream di Server-Sent Events scoped alla richiesta — il wire format che il Capitolo 14 ha parsato a mano. Le semantiche sono identiche su entrambi, perché un transport è un binding: definisce framing e consegna, non significato.4
La prima cosa sbagliata: non c’era nessuna versione
Link alla sezione: La prima cosa sbagliata: non c’era nessuna versioneIl comando sopra ha inviato tools/list e nient’altro. Secondo la revisione attuale quella richiesta è malformata, e un server conforme deve rifiutarla.
Dal 2026-07-28, MCP è un protocollo stateless, e la specifica lo dichiara senza sfumature:
The Model Context Protocol (MCP) is a stateless protocol: all the information needed to process a request is contained in the request itself. A server processes each request independently; no state should be inferred from previous requests, even those on the same connection or stream.2
Quindi ogni richiesta porta la propria versione del protocollo e le proprie capability del client, in un oggetto riservato _meta dentro params. Due di quei campi sono richiesti in ogni singola richiesta; una richiesta a cui manchi uno dei due è malformata e il server deve rispondere -32602:2
chiave _meta | richiesta | che cos’è |
|---|---|---|
io.modelcontextprotocol/protocolVersion | sì | la revisione parlata da questa richiesta, per es. "2026-07-28" |
io.modelcontextprotocol/clientCapabilities | sì | cosa il client può fare per il server in questa richiesta |
io.modelcontextprotocol/clientInfo | no (ma dovrebbe) | nome e versione del client, solo per display e log |
io.modelcontextprotocol/logLevel | no | il livello minimo di log che il server dovrebbe emettere per questa richiesta |
Scritta per intero, una tools/list corretta è questa — ed è l’ultima volta che questo capitolo mostra i metadati per intero, perché da qui in poi sono su ogni richiesta:
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{"elicitation":{"form":{}}},
"io.modelcontextprotocol/clientInfo":{"name":"bare-hands","version":"0.0.1"}}}}L’oggetto delle capability è la negoziazione. Non c’è più un passaggio di negoziazione separato: il client dichiara cosa può fare in ogni richiesta, il server dichiara cosa può fare nel risultato, e nessuna delle due parti può usare una feature che l’altra non ha dichiarato. Un server che ha bisogno di una capability non dichiarata dal client deve rispondere -32021 e nominare la capability mancante in data.requiredCapabilities. Un server che non parla la versione richiesta deve rispondere -32022 ed elencare le versioni che parla.2
I client che vogliono la risposta in anticipo possono chiederla: server/discover è una RPC obbligatoria che restituisce versioni supportate, capability, identità e un blocco opzionale di instructions in un solo round trip.5 Chiamarla è opzionale. Implementarla no.
La seconda cosa sbagliata: il server era legacy
Link alla sezione: La seconda cosa sbagliata: il server era legacyIl comando ha funzionato. Secondo la revisione attuale non avrebbe dovuto, e il motivo per cui lo ha fatto merita una misurazione più che un paragrafo, perché in una riga racconta lo stato dell’intero ecosistema.
Sonda il reference server nel modo in cui la specifica dice a un client moderno di sondare:
echo '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{}}}}' \
| npx @modelcontextprotocol/server-everything stdio{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}Questo è il terzo ramo della regola di compatibilità: un DiscoverResult significa moderno, un errore moderno riconosciuto significa moderno-ma-versione-sbagliata, e qualunque altra cosa — incluso -32601 — significa legacy, torna all’handshake initialize.3 Quindi fallo, chiedendo la revisione attuale:
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28",
"capabilities":{},"clientInfo":{"name":"bare-hands","version":"0.0.1"}}}
← {"result":{"protocolVersion":"2025-11-25","capabilities":{"tools":{"listChanged":true},
"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},
"logging":{},"tasks":{…},"completions":{}},"serverInfo":{"name":"mcp-servers/everything",
"title":"Everything Reference Server","version":"2.0.0"},"instructions":"…"}}Il client ha chiesto 2026-07-28 e il server ha risposto 2025-11-25. Il 7 settembre 2026, il reference server ufficiale — package npm @modelcontextprotocol/server-everything, versione 2026.8.31, pubblicato il 31 agosto 2026 — non implementa la revisione attuale. Né, guardando le date, lo fa l’SDK TypeScript su cui è costruito: la release 1.30.0 è uscita il 27 luglio 2026, il giorno prima della revisione.
Leggi la conseguenza, non il gossip. Quasi tutto ciò che è stato scritto su MCP descrive un protocollo con un handshake initialize, una sessione, una richiesta roots/list che il server invia al client, e un transport HTTP+SSE. Tutte e quattro le cose sono sparite o stanno sparendo. Quando leggi qualsiasi cosa su MCP, inclusa questa pagina, la prima cosa da cercare è un numero di revisione.
E il motivo per cui il primissimo comando ha funzionato è dichiarato nella specifica come un rischio, non una feature:
some legacy servers do not validate that a request arrives after
initializeand would process an era-ambiguous method (such astools/call) under legacy semantics. Probing yields a deterministic failure instead.3
Misurato: inviare tools/list a quel server senza alcun handshake restituisce l’intero catalogo. Un metodo che avrebbe dovuto essere rifiutato è stato servito, ed è esattamente per questo che la specifica dice di sondare prima con server/discover anche quando supporti solo versioni moderne.
Tre ruoli, e la frase da citare dell’intero documento
Link alla sezione: Tre ruoli, e la frase da citare dell’intero documentoMCP ha tre parti, e la distinzione tra le prime due è quella che le persone collassano:
Host. L’applicazione: il prodotto di chat, l’editor, l’agent. Possiede la conversazione, il modello, le credenziali e il consenso dell’utente. Crea client e applica il confine di sicurezza tra loro.
Client. Un connector dentro l’host. Ogni client parla con esattamente un server — una relazione 1:1 stretta — e allega la versione del protocollo e le capability a ogni richiesta che instrada.
Server. Un processo o un servizio che espone risorse, tool e prompt. Può essere locale o remoto, opera in modo indipendente, e il suo intero compito è un’area focalizzata.6
Quella regola «esattamente un server» non è contabilità. È ciò che rende implementabile il principio di design qui sotto, e questa è la frase da portare via dalla specifica se ne porti via una sola:
Servers should not be able to read the whole conversation, nor "see into" other servers. Servers receive only necessary contextual information. Full conversation history stays with the host. Each server maintains isolation. Cross-server interactions are controlled by the host.6
Ribalta il modello mentale con cui arrivano quasi tutti. Un server meteo che colleghi al tuo assistente non vede cosa hai chiesto. Vede un tools/call con gli argomenti scelti dal modello, e nient’altro: non i turni precedenti, non il tuo system prompt, non i risultati restituiti dal server calendario un attimo prima. Se due server devono cooperare, l’host porta un valore dall’uno all’altro, deliberatamente, perché il modello lo ha chiesto. È per questo che l’isolamento è la proprietà di sicurezza su cui si appoggia il Capitolo 30: un server compromesso ha un blast radius piccolo e definito, e ampliarlo richiede la cooperazione dell’host.
La terza cosa: tre primitive, ordinate per chi comanda
Link alla sezione: La terza cosa: tre primitive, ordinate per chi comandaIl primo comando ha chiesto a quel server i tool e ne ha ottenuti tredici. Fagli le altre due domande e risponde anche a quelle: resources/list ne restituisce sette, prompts/list ne restituisce quattro. Nessuno di questi è apparso, perché niente lo ha chiesto. Questo ci porta alla spina dorsale didattica di MCP, presente nella specifica come una tabella che quasi nessuno cita:
| Primitive | Controllo | Descrizione | Esempio |
|---|---|---|---|
| Prompt | Controllato dall’utente | Template interattivi invocati per scelta dell’utente | Comandi slash, opzioni di menu |
| Risorse | Controllate dall’applicazione | Dati contestuali allegati e gestiti dal client | Contenuti di file, cronologia git |
| Tool | Controllati dal modello | Funzioni esposte all’LLM per eseguire azioni | Richieste API POST, scrittura di file |
Non «tre modi per esporre una capability». Tre risposte a chi decide che questo accade. Il modello decide di chiamare un tool. L’applicazione decide di allegare una risorsa. La persona decide di eseguire un prompt. Se sbagli questo, la feature funziona comunque, ma funziona nel momento sbagliato e per il motivo sbagliato.
Il modo più chiaro per sentirlo è un calendario. Ecco un server che espone lo stesso calendario tre volte, una per ciascuna primitive, in cento righe di semplice Node senza dipendenze:
const TOOL = {
name: "create_event",
description: "Create a calendar event. Writes to the calendar.",
inputSchema: {
type: "object",
properties: {
title: { type: "string", description: "Event title." },
startsAt: { type: "string", format: "date-time", description: "Start, ISO 8601 UTC." },
},
required: ["title"],
},
};
switch (method) {
case "resources/read":
return ok(id, { contents: [{ uri: "calendar://week",
mimeType: "application/json", text: JSON.stringify(EVENTS) }],
ttlMs: 60000, cacheScope: "private" });
case "prompts/get":
return ok(id, { description: PROMPT.description, messages: [{ role: "user",
content: { type: "text", text: `Read calendar://week and draft a plan. ` +
`Focus: ${params.arguments?.focus ?? "balance"}.` } }] });
case "tools/list":
return ok(id, { tools: [TOOL], ttlMs: 300000, cacheScope: "public" });
}Eseguilo e interrogalo in tutti e tre i modi. Output reale, un messaggio per riga sul wire, qui mandato a capo per la pagina, con la richiesta _meta e il blocco di identità del server omessi:
→ resources/read {"uri":"calendar://week"}
← {"resultType":"complete","contents":[{"uri":"calendar://week",
"mimeType":"application/json","text":"[{\"id\":\"e1\",\"title\":\"Standup\",
\"startsAt\":\"2026-09-07T09:00:00Z\"},{\"id\":\"e2\",\"title\":\"Design review\",
\"startsAt\":\"2026-09-09T15:00:00Z\"}]"}],"ttlMs":60000,"cacheScope":"private"}
→ prompts/get {"name":"prepare_week","arguments":{"focus":"deep work"}}
← {"resultType":"complete","description":"Read the week and draft a plan.",
"messages":[{"role":"user","content":{"type":"text",
"text":"Read calendar://week and draft a plan. Focus: deep work."}}]}
→ tools/call {"name":"create_event","arguments":{"title":"Dentist",
"startsAt":"2026-09-10T08:30:00Z"}}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],
"structuredContent":{"id":"e3","title":"Dentist","startsAt":"2026-09-10T08:30:00Z"},
"isError":false}Tre metodi, tre forme, un calendario. Ora il punto:
Leggere la settimana è una risorsa
Link alla sezione: Leggere la settimana è una risorsaÈ indirizzata da un URI, è inerte, e l’applicazione decide se allegarla alla conversazione. Niente nel protocollo permette al modello di prenderla da solo. Il risultato porta ttlMs e cacheScope, nuovi in questa revisione, così il client può mettere la settimana in cache per un minuto invece di fare polling.
Creare un evento è un tool
Link alla sezione: Creare un evento è un toolHa uno schema, ha effetti collaterali, e il modello decide quando chiamarlo. Il suo risultato porta isError, che è il campo sostenuto dal Capitolo 18: un fallimento di validazione torna come risultato del tool che il modello può leggere e correggere, non come errore di protocollo.
«Prepara la mia settimana» è un prompt
Link alla sezione: «Prepara la mia settimana» è un promptÈ un template nominato, con argomenti, che la persona invoca: il comando slash nel menu. Restituisce messaggi, non una risposta. È un modo per l’autore di un server di spedire la formulazione che funziona con i propri tool, che è esattamente la conoscenza che l’autore del server ha e l’utente no.
Quasi tutti trasformano tutte e tre queste cose in tool. Il risultato è un catalogo in cui una lettura che l’applicazione avrebbe dovuto allegare in silenzio compete per l’attention del modello con una scrittura che richiede approvazione, e in cui l’unica cosa per cui una persona voleva un pulsante è sepolta in uno schema. Non costa niente farlo bene, e si decide prima di scrivere una riga.
Il server non può chiamarti
Link alla sezione: Il server non può chiamartiIl tool calendario ha un argomento richiesto, title, e uno opzionale, startsAt. Chiedigli di creare un evento senza data, e torna qualcosa di interessante:
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"}}
← {"resultType":"input_required",
"inputRequests":{"when":{"method":"elicitation/create","params":{"mode":"form",
"message":"When should \"Dentist\" start?",
"requestedSchema":{"type":"object",
"properties":{"startsAt":{"type":"string","format":"date-time"}},
"required":["startsAt"]}}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}Il server non ha inviato una richiesta. Ha risposto a quella che gli era stata data, con resultType: "input_required" e una descrizione di ciò che gli serve ancora. Il client raccoglie la risposta dalla persona, e poi reinvia la chiamata originale — con un nuovo id, portando inputResponses e riecheggiando l’requestState opaco:
→ tools/call {"name":"create_event","arguments":{"title":"Dentist"},
"inputResponses":{"when":{"action":"accept",
"content":{"startsAt":"2026-09-10T08:30:00Z"}}},
"requestState":"eyJ0aXRsZSI6IkRlbnRpc3QifQ=="}
← {"resultType":"complete","content":[{"type":"text",
"text":"Created e3: Dentist at 2026-09-10T08:30:00Z"}],"isError":false}Queste sono le Multi Round-Trip Requests, introdotte nella revisione attuale, e hanno sostituito il design precedente in cui i server inviavano richieste JSON-RPC ai client. La specifica dei transport ora enuncia la regola in modo netto: «servers do not initiate JSON-RPC requests and clients do not send JSON-RPC responses».4 C’è una sola direzione dell’iniziativa, e appartiene all’host.
Due feature lato client viaggiano su quel meccanismo, e una delle due ha un nome che ti farà inciampare.
Elicitation è il server che chiede qualcosa alla persona: un form con un JSON Schema volutamente ristretto — oggetti piatti, proprietà primitive, niente annidamento — così qualunque client può renderizzarlo senza un layout engine. Porta una regola rigida: i server non devono usare la modalità form per chiedere «passwords, API keys, access tokens, or payment credentials», e devono usare la modalità URL per quelle cose, che manda l’utente a una pagina che il client non legge mai.7
Sampling è il server che chiede una generazione al modello dell’host, così un server può essere intelligente senza detenere una API key. Ed ecco l’avviso di vocabolario, perché questa parola significa già qualcos’altro in questo corso: questo non è il sampling del Capitolo 17. Qui non c’entra niente la temperature, il top-p o la forma di una distribuzione di probabilità. È una chiamata annidata a un modello che viaggia all’indietro attraverso un protocollo.
C’è una seconda ragione per non usarlo con leggerezza: a partire da questa revisione, sampling è deprecato, insieme a roots e logging, sotto SEP-2577, con una migrazione suggerita in modo brutale: «integrate directly with LLM provider APIs instead of Sampling».8 L’idea non è fallita tecnicamente; non è riuscita a giustificare la sua superficie, e un protocollo che può rimuovere cose è più sano di uno che non può.
Rompilo di proposito: le connessioni non sono sessioni
Link alla sezione: Rompilo di proposito: le connessioni non sono sessioniLa statelessness sembra un dettaglio di wire format finché non la testi. Prendi lo scambio di tre messaggi sopra ed esegui ogni messaggio in un processo separato: un node calendar.mjs fresco, nessuna memoria condivisa, niente portato oltre:
process A tools/call (no date) → resultType: input_required
requestState: eyJ0aXRsZSI6IkRlbnRpc3QifQ==
process B tools/call (with the answer, same requestState)
→ resultType: complete
"Created e3: Dentist at 2026-09-10T08:30:00Z"
process C resources/read calendar://week
→ events: 2 (Standup, Design review)Il processo B, che non aveva mai visto la domanda, ha completato una chiamata multi-round-trip iniziata dal processo A. Questo è il punto di requestState: la continuation viaggia nel messaggio, quindi niente dipende dal fatto che il processo sia lo stesso.
Il processo C è il fallimento. L’evento è stato creato e non è lì: perché il server giocattolo mantiene EVENTS in un array a livello di modulo, e un array a livello di modulo è stato della connessione. La nota della specifica nomina l’errore con precisione:
an open connection, such as a STDIO process, is not a conversation or session: clients may interleave unrelated requests on the same transport, and a server must not treat connection or process identity as a proxy for conversation or session continuity.2
La correzione prescritta non è una sessione. È un handle esplicito: un tool di creazione restituisce un identificatore opaco, e ogni chiamata successiva lo prende come un argomento ordinario. Il protocollo non ne ha alcun concetto: «from the wire's perspective a handle is an ordinary string in a tool result and an ordinary argument to subsequent tool calls».9 Il che mette il modello incaricato di portarlo, e il server incaricato di validare che questo chiamante sia autorizzato a usarlo in ogni singola chiamata, perché un handle è un nome e non un permesso.
Quanto costa un server prima di fare qualsiasi cosa
Link alla sezione: Quanto costa un server prima di fare qualsiasi cosaOgni tool esposto da un server è uno schema che entra nel tuo prompt a ogni richiesta, e il Capitolo 24 ha misurato cosa fa a una context window. MCP aggiunge una seconda voce di costo che è facile perdere, quindi vale la pena contarle entrambe sul reference server sopra.
13 tool definitions (name + description + inputSchema): 1,307 tokens
cheapest tool, get-tiny-image 52
costliest tool, gzip-file-as-resource 235
server `instructions`, returned by discovery: 312 tokens
------
one server, connected, before it is used: 1,619 tokensDue osservazioni. La prima è aritmetica: collega cinque server di queste dimensioni e circa ottomila token della tua context window sono occupati a ogni turno, per sempre, che il modello ne usi qualcuno oppure no. Questo è il meccanismo dietro la riduzione da 150.000 a 2.000 citata dal Capitolo 24, e il motivo per cui esiste la scoperta dei tool just-in-time.
La seconda è una nota di sicurezza travestita da contabilità. instructions è testo in linguaggio naturale, scritto dall’autore del server, che atterra nel prompt dell’host, e le descrizioni dei tool accanto sono la stessa cosa. La specifica dice cosa farne nei suoi principi di sicurezza: annotazioni e descrizioni dei tool «should be considered untrusted, unless obtained from a trusted server», e gli host «must obtain explicit user consent before invoking any tool».1 Collegare un server MCP non è aggiungere una dependency. È concedere a uno sconosciuto 1.619 token del tuo system prompt e il diritto di essere chiamato. Il Capitolo 30 è ciò che accade quando quello sconosciuto è ostile.
Sezione datata: la revisione 2026-07-28, e cosa rompe
Link alla sezione: Sezione datata: la revisione 2026-07-28, e cosa rompeTutto in questa sezione è vero per la revisione del protocollo 2026-07-28, quella attuale, letta il 7 settembre 2026. Le revisioni sono datate YYYY-MM-DD e la data è l’ultima volta in cui è stata fatta una modifica non retrocompatibile.10 Il documento normativo è un file TypeScript, schema/2026-07-28/schema.ts; il JSON Schema accanto è generato da quello, ed è per questo che qui la specifica viene letta in TypeScript e perché insegnare MCP da qualunque altra cosa significa insegnare una traduzione.
| Cosa è cambiato | Era | Ora è | Rompe |
|---|---|---|---|
| L’handshake | initialize + notifications/initialized, una volta per connessione | rimosso; ogni richiesta porta versione e capability _meta | ogni client scritto prima di questa revisione |
| Sessioni | header Mcp-Session-Id, stato scoped alla connessione | rimosse; lo stato viaggia in handle espliciti coniati dal server | endpoint di lista che variavano per connessione |
| Discovery | dedotta dal risultato initialize | server/discover, che i server devono implementare | niente, ma ora è obbligatorio implementarla |
| Chiamate server-to-client | il server inviava roots/list, sampling/createMessage, elicitation/create | InputRequiredResult e un retry del client | ogni server che spingeva una richiesta a un client |
| Forma del risultato | qualunque oggetto | resultType richiesto: "complete" o "input_required" | niente: un campo assente deve essere letto come "complete" |
| Subscriptions | stream HTTP GET, resources/subscribe | un solo stream subscriptions/listen con tipi opt-in | l’endpoint GET è sparito |
| Ripresa dello stream | replay Last-Event-ID su Streamable HTTP | rimosso; uno stream rotto perde la richiesta, re-inviala con un nuovo id | client che facevano affidamento sulla riconsegna |
| Roots | una feature client che i server potevano chiedere | deprecata (SEP-2577); passa i path come argomenti di tool o URI di risorse | niente per ora: finestra di dodici mesi |
| Sampling e logging | feature client | deprecati (SEP-2577) | niente per ora: finestra di dodici mesi |
| Transport HTTP+SSE | deprecato da 2025-03-26 | Deprecated secondo la lifecycle policy (SEP-2596) | migra a Streamable HTTP |
| Registrazione client | OAuth 2.0 Dynamic Client Registration, RFC 7591 | deprecata a favore dei Client ID Metadata Documents | mantenuta per authorization server che non li hanno |
| Codici di errore | -32002 per risorsa non trovata | -32602; -32020–-32099 riservati alla specifica | nuovi codici -32020, -32021, -32022 |
Il cambiamento di governance sotto quella tabella conta più di qualsiasi singola riga. Questa revisione ha adottato una feature lifecycle and deprecation policy: le feature sono Active, Deprecated o Removed, una feature deprecata documenta il suo percorso di migrazione e resta nella specifica per almeno dodici mesi prima di diventare candidata alla rimozione, e c’è un registro che elenca tutto ciò che attualmente è nello stato Deprecated.8 Prima di quella policy, «deprecated» in un protocollo AI significava qualunque cosa dicesse l’ultimo blog post. Ora significa una data.
Mostra dettagli
Extensions, la parte di cui nessuno ha ancora scritto.
Oltre al core, MCP definisce extensions opzionali: «always opt-in and require explicit support from both client and server», dichiarate tramite un campo extensions nelle capability del client e del server.1 Tre vale la pena conoscerle per nome:
- Tasks (
io.modelcontextprotocol/tasks), spostate fuori dal protocollo core in un’extension ufficiale in questa revisione: esecuzione asincrona di operazioni long-running, con polling tramitetasks/get, input a metà corsa tramitetasks/update, e handle duraturi. È la risposta a un tool che impiega venti minuti, che il Capitolo 23 ha gestito con un evento di progresso e un segnale che raggiunge il tool. - Skills over MCP, un working group che rende le skill degli agent — il tema del Capitolo 28 — scopribili e consumabili attraverso il protocollo.
- MCP Apps, UI interattive renderizzate inline nella conversazione: grafici, form, player video.
E nota cosa significa ora «negoziato»: non c’è un’inizializzazione in cui negoziare, quindi un’extension è dichiarata per richiesta come tutto il resto.
Dove si colloca MCP, rispetto a tutto ciò con cui viene confuso
Link alla sezione: Dove si colloca MCP, rispetto a tutto ciò con cui viene confusoQuesto è il vocabolario dell’intero blocco in un posto solo.
| Che cos’è | Chi parla con chi | Quando è la risposta | |
|---|---|---|---|
| Una semplice API | Un’interfaccia per un programma | il tuo codice ↔ un servizio | Stai scrivendo il chiamante. Controlli lo schema, l’auth e la gestione degli errori, e non c’è nessun problema di discovery da risolvere. |
| MCP | Un protocollo per esporre tool, dati e template a un’applicazione AI | host ↔ server, un client ciascuno | Qualcun altro ha scritto la capability e molti host dovrebbero poterla usare senza un’integrazione bespoke. |
| RAG | Una tecnica per trovare testo e metterlo nel prompt | il tuo codice ↔ il tuo indice | Il modello deve sapere qualcosa. Capitolo 19. MCP è un modo per consegnare un retriever; non è un retriever. |
| Agent skills | Una cartella con un SKILL.md che il modello legge | modello ↔ un documento | La conoscenza è procedurale — come noi facciamo questa cosa — ed è prosa, non una funzione. Capitolo 28. |
| A2A | Un protocollo perché gli agent collaborino da pari | agent ↔ agent | L’altro lato ragiona, pianifica e mantiene stato lungo un task esteso, invece di rispondere a una chiamata. |
| ACP | Era un protocollo separato di comunicazione tra agent | — | Non è più un confronto vivo. Vedi sotto. |
Due di queste meritano una frase ciascuna, perché è lì che vive davvero la confusione.
MCP rispetto ad A2A non è una rivalità, e lo dicono entrambe le specifiche. La documentazione A2A traccia la linea in base a cosa c’è dall’altra parte: MCP «defines how an AI agent interacts with and utilizes individual tools and resources, such as a database or an API», dove un tool esegue «specific, often stateless, functions»; A2A riguarda gli agent, «more autonomous systems» che «reason, plan, use multiple tools, maintain state over longer interactions, and engage in complex, often multi-turn dialogues». Il suo riassunto è la frase da ricordare: «A2A is about agents partnering on tasks, while MCP is more about agents using capabilities.»11 I due si annidano: un’applicazione usa A2A per raggiungere altri agent, e ogni agent usa MCP per raggiungere i propri tool. Il Capitolo 25 ha tracciato quella linea dentro un solo processo, tra chiedere a un sub-agent e passargli la conversazione; A2A la traccia tra organizzazioni.
MCP rispetto ad ACP è un confronto con una premessa scaduta, ed è esattamente per questo che vale la pena rispondervi. L’Agent Communication Protocol era uno standard aperto separato per la messaggistica agent-to-agent. La sua stessa documentazione ora si apre con l’avviso: «ACP is now part of A2A under the Linux Foundation!»12 La risposta onesta a «MCP o ACP?» nel settembre 2026 è che la domanda ha un’opzione in meno rispetto a quanto suggeriscano le pagine che rankano per essa.
E il confronto che le persone chiedono di più, mcp vs api, ha la risposta meno interessante: MCP è una API. Ciò che aggiunge non è potenza, sono convenzioni: un set fisso di nomi di metodi, una chiamata di discovery, una gerarchia di controllo sulle primitive, e un modello di isolamento. Rinunci alla libertà di progettare la tua interfaccia e ottieni ogni host che parla il protocollo, che è il trade-off che ogni protocollo ha sempre offerto.
Dove si va ora
Link alla sezione: Dove si va oraOra puoi leggere la specifica senza un traduttore, distinguere una risorsa da un tool da un prompt in base a chi ne ha il controllo, digitare una richiesta a mano quando una libreria client ti sta mentendo, e datare qualunque articolo su MCP leggi in base a quali feature deprecate insegna ancora come attuali.
Quello che non hai fatto è spedirne uno. Il Capitolo 27 scrive lo stesso server due volte: TypeScript e Python, fianco a fianco, perché MCP è l’unico territorio davvero bilingue di questo corso e i numeri lo dicono in entrambe le direzioni. Copre correttamente i due transport live, l’inspector, il packaging, e la metà del protocollo che questo capitolo ha lasciato deliberatamente da parte: authorization. Perché nel momento in cui il tuo server è remoto invece che un sottoprocesso sul tuo laptop, il client di uno sconosciuto presenterà un token, e la regola della specifica su cosa puoi farne è insolitamente severa.
Il che solleva la domanda a cui il prossimo capitolo deve rispondere, e non è una domanda amichevole: se un token arriva al tuo server ed è stato emesso per l’audience di qualcun altro, che cosa ti impedisce esattamente di inoltrarlo?
Fonti e metodo
Link alla sezione: Fonti e metodoOgni citazione, nome di metodo, codice di errore e regola in questo capitolo è stata letta dalla specifica Model Context Protocol, revisione 2026-07-28, il 7 settembre 2026. Ogni trace è stato prodotto localmente su Node 22: il server calendario giocattolo è di 101 righe senza dipendenze, e il reference server è il package npm pubblicato nominato sotto. Nessuna API a pagamento è stata chiamata per scrivere questo capitolo: qui nulla richiede un modello, che è già il punto.
Le misurazioni: @modelcontextprotocol/server-everything@2026.8.31, pubblicato il 31 agosto 2026, costruito su @modelcontextprotocol/sdk@1.30.0, pubblicato il 27 luglio 2026 — un giorno prima della revisione descritta da questo capitolo. Risponde a server/discover con -32601, negozia 2025-11-25 quando gli viene chiesto 2026-07-28, e serve tools/list senza alcun handshake. Il suo catalogo è di 13 tool in 7.663 byte; i conteggi dei token sono o200k_base tramite tiktoken, sopra name, description e inputSchema di ogni definizione, che è ciò che un provider renderizza nel tuo prompt e non ciò che pesa il frame JSON-RPC.
Anthropic, Code execution with MCP: building more efficient agents, 4 novembre 2025, è la fonte della cifra da 150.000 a 2.000, citata e usata nel Capitolo 24 e qui solo richiamata.
Riferimenti
Link alla sezione: Riferimenti-
Specification,
modelcontextprotocol.io/specification/latest(redirect a/2026-07-28), letta il 7 settembre 2026. Fonte del confronto con il Language Server Protocol; della dichiarazione che la specifica è «based on the TypeScript schema inschema.ts»; del riepilogo del protocollo base («Stateless, self-contained requests», «Per-request capability negotiation»); della lista di extension (Tasks, Skills over MCP, MCP Apps) e della dichiarazione che le extensions «are always opt-in and require explicit support from both client and server»; e dei principi di Security and Trust & Safety, inclusi «Hosts must obtain explicit user consent before invoking any tool» e il trattamento delle annotazioni dei tool come untrusted. ↩ ↩2 ↩3 -
Base Protocol,
modelcontextprotocol.io/specification/2026-07-28/basic. Fonte dei vincoli JSON-RPC (id non-null, nessun riuso dell’id,resultTyperichiesto); della sezione Statelessness e della sua nota secondo cui un processo stdio aperto non è una sessione; della tabella delle chiavi riservate_metae dello stato richiesto/opzionale di ogni campo per richiesta; della regola-32602per un campo richiesto mancante; della regolaMissingRequiredClientCapability(-32021); e della policy di allocazione dei codici di errore. ↩ ↩2 ↩3 ↩4 ↩5 -
stdio transport,
modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio. Fonte delle regole di framing delimitate da newline, del requisito di purezza distdout, dell’autorizzazione perstderr, e della sonda di retrocompatibilità a tre esiti — incluso il warning secondo cui alcuni server legacy processano metodi era-ambiguous senza handshake, cosa che la misurazione in questo capitolo riproduce. ↩ ↩2 ↩3 -
Transports overview,
modelcontextprotocol.io/specification/2026-07-28/basic/transports. Fonte dell’inquadramento «a transport is a binding» e della dichiarazione secondo cui i server non iniziano richieste JSON-RPC e i client non inviano risposte JSON-RPC. ↩ ↩2 -
Discovery,
modelcontextprotocol.io/specification/2026-07-28/server/discover. Fonte dello status obbligatorio diserver/discover, della forma diDiscoverResult, e del campoinstructionsdescritto come «optional natural-language guidance for LLMs on how to use this server effectively». ↩ -
Architecture,
modelcontextprotocol.io/specification/2026-07-28/architecture. Fonte delle definizioni di host/client/server, della regola client-to-server 1:1, dei quattro principi di design, di cui qui si cita il principio di isolamento senza il quinto bullet, «Host process enforces security boundaries», e della sezione sulla capability negotiation. ↩ ↩2 -
Elicitation,
.../client/elicitation, e Sampling,.../client/sampling. Fonte delle due modalità di elicitation e del loro schema ristretto; del divieto di richiedere credenziali tramite modalità form; della definizione di sampling, del suo requisito human-in-the-loop, e del warning di deprecazione associato. ↩ -
Key Changes,
modelcontextprotocol.io/specification/2026-07-28/changelog, e Feature lifecycle and deprecation policy,.../community/feature-lifecycle. Fonte di ogni riga della tabella delle modifiche: rimozione delle sessioni e dell’headerMcp-Session-Id(SEP-2567); statelessness e rimozione diinitialize(SEP-2575);server/discover(SEP-2575);subscriptions/listen(SEP-2575); Multi Round-Trip Requests eresultType(SEP-2322); rimozione della resumability dello stream (SEP-2575); deprecazione di Roots, Sampling e Logging (SEP-2577); riclassificazione di HTTP+SSE (SEP-2596); deprecazione della Dynamic Client Registration a favore dei Client ID Metadata Documents; rinumerazione dei codici di errore; e finestra di deprecazione di dodici mesi. ↩ ↩2 -
Tools,
modelcontextprotocol.io/specification/2026-07-28/server/tools, e Server Features,.../server. Fonte della tabella della gerarchia di controllo riprodotta sopra; delle formetools/listetools/call; della distinzioneisErrortra errori di protocollo ed errori di esecuzione del tool; delle regole sui nomi dei tool e della nota sui namespace che raccomanda «prefixing tool names with a server identifier»; e della guida non normativa «Stateful Tools» sugli handle espliciti. ↩ -
Versioning,
modelcontextprotocol.io/specification/versioning. Fonte dello schemaYYYY-MM-DD, degli stati di revisione Draft/Current/Final, della conferma che 2026-07-28 è current, e delle regole di negoziazione per richiesta. La tabella dei tier degli SDK amodelcontextprotocol.io/docs/sdkelenca TypeScript, Python, C#, Go e Rust al Tier 1, Java e Ruby al Tier 2, e Swift, PHP e Kotlin al Tier 3. ↩ -
A2A Protocol, versione 1.0.0,
a2a-protocol.org— la specifica e la pagina A2A and MCP: Relationship and Distinction, lette il 7 settembre 2026. Fonte della distinzione tools-against-agents, della dichiarazione che i due protocolli «address distinct but highly complementary needs», e della formulazione partnering/using. ↩ -
Agent Communication Protocol,
agentcommunicationprotocol.dev, letto il 7 settembre 2026: «ACP is now part of A2A under the Linux Foundation!», un banner aggiunto sopra una specifica ancora servita per intero — architecture, agent manifest, agent discovery, message structure, stateful agents, run lifecycle e lista degli endpoint REST rispondono ancora tutti 200. La specifica non è sparita; il progetto sì. ↩