Il 28 luglio 2026 il Model Context Protocol (MCP) ha cambiato architettura. La revisione 2026‑07‑28 della specifica ha eliminato l’handshake initialize e le sessioni, così un MCP server remoto si comporta come un normale servizio HTTP dietro un load balancer. Chi ha imparato il protocollo con i tutorial del 2025 trova oggi esempi costruiti su un modello stateful che l’ecosistema sta abbandonando. Il codice che segue parte invece dalla versione corrente dello SDK Python.
L’interesse per gli agenti AI non è più una nicchia. Secondo la Developer Survey 2025 di Stack Overflow, il 31 % degli sviluppatori usava già agenti AI e un altro 17 % pensava di adottarli. Un agente però vale quanto gli strumenti che può chiamare. MCP è il formato comune per dichiarare quegli strumenti una sola volta e farli usare a qualunque applicazione compatibile: un IDE, un assistente da terminale o uno script Python scritto da te.
Cos’è un MCP server e cosa è cambiato con la specifica 2026‑07‑28
Un MCP server è un programma che dichiara capacità a un’applicazione AI in modo standard. Il protocollo, documentato sul sito ufficiale, distingue tre attori. L’host è l’applicazione con cui parla l’utente, per esempio un assistente di programmazione. Il client vive dentro l’host e parla MCP. Il server è quello che scrive lo sviluppatore, e non dialoga mai direttamente con il modello. Lo SDK Python offre anche una classe Client, la stessa che userebbe un host, ed è quella che servirà per l’agente e per i test.
Un server può esporre tre tipi di primitive. La differenza sta in chi decide di usarle.
- Tool: funzione che il modello chiama per agire (es.
cercanote,salvanota). - Resource: dati caricati nel contesto del modello (es.
nota://checklist-sicurezza). - Prompt: modello di messaggio richiamabile per nome (es.
riassumi).
Il paragone con il web aiuta. Una resource somiglia a una GET, perché legge e non modifica nulla. Un tool somiglia a una POST, perché esegue lavoro e può avere effetti collaterali. Il prompt non ha un equivalente HTTP ed è più vicino a una query salvata.
Principali modifiche della revisione del 28 luglio 2026
La revisione è la modifica più pesante dell’ultimo anno. Rende il protocollo stateless per impostazione predefinita, introduce un framework di estensioni formale (con MCP Apps per le interfacce renderizzate dal server e Tasks per i lavori lunghi) e rafforza l’autorizzazione, allineandola a OAuth e OpenID Connect. Ecco cosa cambia per chi scrive codice.
| Prima | Dal 28 luglio 2026 |
|---|---|
Handshake initialize e sessioni | Nessun handshake, nessuna sessione |
| Negoziazione iniziale | Scoperta tramite server/discover |
| Richieste avviate dal server | Multi Round‑Trip Requests (MRTR) |
| Notifiche di cambiamento sparse | Unificate sotto subscriptions/listen |
| Tasks nel nucleo del protocollo | Tasks diventa un’estensione |
| Roots, Sampling, Logging, HTTP+SSE | Marcati come deprecati |
Due conseguenze pratiche. Primo, non devi gestire sessioni nel codice del server, e un server remoto può stare dietro un normale bilanciatore round‑robin senza sticky session. Secondo, tutto ciò che trovi online con initialize o con il trasporto SSE descrive il vecchio modello. Lo SDK resta compatibile con i client di epoche precedenti, e per questo la stessa libreria parla con server vecchi e nuovi.
Il progetto Notedesk: architettura e prerequisiti
Notedesk ha due metà che comunicano solo attraverso MCP. Il server (server.py) conosce le note e nient’altro. L’agente (agent.py) conosce il modello e nient’altro. In mezzo c’è il protocollo, e questa separazione permette di cambiare modello senza toccare il server, o di collegare lo stesso server a un host diverso.
La tabella elenca i file che avrai alla fine.
| File | Ruolo | Passo |
|---|---|---|
| pyproject.toml | Dipendenze e versione di Python | 1 |
| dati/*.md | Tre note di esempio | 2 |
| server.py | MCP server con 4 tool, 1 resource, 1 prompt | 3, 5, 6, 7 |
| test_server.py | Cinque test in memoria con pytest | 8 |
| client_prova.py | Client stdio per ispezionare il server | 9 |
| agent.py | Agente con modello locale e ciclo di tool | 10, 11 |
Strumenti consigliati
- Python: 3.15.1 consigliata, ammesse 3.14.9 e 3.13.17. Lo SDK richiede almeno 3.11.
- SDK:
mcpserie 2.x, installa con extraclipermcp devemcp run. - uv: gestisce ambiente e dipendenze; in alternativa
venvepip. - Ollama: ultima versione stabile, espone endpoint compatibile con le API OpenAI su
localhost:11434/v1. - Modello locale: con capability tools, ad esempio
Qwen3.8-27Bscaricato tramite Ollama. - Node.js: LTS corrente, serve solo all’MCP Inspector del passo 4.
Python 3.15.0 è uscito il 9 ottobre 2026 e la 3.15.1 il 21 ottobre con tre aggiornamenti di sicurezza paralleli per i rami 3.15, 3.14 e 3.13. Se sulla macchina hai ancora una 3.10, aggiornala: oltre a non ricevere più patch, non soddisfa il requisito dello SDK. L’ultima release dello SDK Python trovata è la 2.3.0 del 2 ottobre 2026; se ne vedi una più alta, usa quella.
Gestione dei rischi e sicurezza
Prima di scrivere codice conviene fissare cosa può andare storto. In un agente con tool le minacce arrivano da tre direzioni.
- Il modello può sbagliare gli argomenti per semplice imprecisione.
- Il testo di una nota può contenere istruzioni nascoste che il modello scambia per ordini dell’utente, la cosiddetta prompt injection indiretta.
- Un componente di terze parti può avere vulnerabilità proprie.
Notedesk risponde a ciascuna con una misura concreta: validazione degli input, strumenti di scrittura limitati e confermati dall’utente, superficie ridotta al minimo. Nessuna è perfetta, ma insieme riducono il danno massimo di un errore del modello a una nota nuova, mai sovrascritta, nella cartella dati.
Passi 1‑3: ambiente, struttura del progetto e primo server
Passo 1: prepara l’ambiente con uv
uv init notedesk
cd notedesk
rm -f main.py
uv add "mcp[cli]" openai
uv add --dev pytest
uv run python --version
L’ultimo comando deve stampare una versione pari o superiore a 3.11. Se uv ha scelto un interprete vecchio, indicane uno più recente nel file pyproject.toml con requires-python e rilancia uv sync. Senza uv, il percorso è python -m venv .venv && source .venv/bin/activate && pip install mcp[cli] openai pytest.
Passo 2: aggiungi le note di esempio
mkdir -p dati
cat > dati/nota1.md <<'EOF'
Checklist sicurezza
- Verifica backup giornaliero
- Controlla aggiornamenti di sicurezza
EOF
cat > dati/nota2.md <<'EOF'
Idea progetto
- Implementare agente AI locale
- Testare con MCP
EOF
cat > dati/nota3.md <<'EOF'
Diario
Oggi ho configurato Notedesk e ho scritto questa guida.
EOF
Passo 3: crea il server base
cat > server.py <<'PY'
import pathlib
from mcp import Server, Tool, Resource, Prompt
BASEPATH = pathlib.Path(file_).parent / "dati"
def lista_note():
return [p.name for p in BASE_PATH.glob("*.md")]
def leggi_nota(nome):
path = BASE_PATH / nome
if not path.is_file():
raise FileNotFoundError(f"Nota {nome} non trovata")
return path.read_text(encoding="utf-8")
def salva_nota(nome, contenuto):
path = BASE_PATH / nome
if path.exists():
raise FileExistsError("Non sovrascrivere note esistenti")
path.write_text(contenuto, encoding="utf-8")
return f"Nota {nome} salvata"
def riassumi_testo(contenuto):
# placeholder, l’agente reale sostituirà questa funzione
return "Riassunto non disponibile in server"
server = Server()
server.registertool(Tool(name="listanote", func=lista_note))
server.registertool(Tool(name="legginota", func=leggi_nota))
server.registertool(Tool(name="salvanota", func=salva_nota))
server.registerresource(Resource(uri="nota://checklist-sicurezza", data=legginota("nota1.md")))
server.registerprompt(Prompt(name="riassumi", func=riassumitesto))
if _name == "main_":
server.run(host="127.0.0.1", port=8000)
PY
Il server ora espone quattro tool (listanote, legginota, salva_nota, riassumi) e una resource (nota://checklist-sicurezza). Avviandolo con python server.py ascolterà su 127.0.0.1:8000.
Test, client e agente locale
Passo 8: test del server
cat > test_server.py <<'PY'
import pytest
from server import server
def testlistanote():
assert "nota1.md" in server.invoketool("listanote")
def testlegginota():
txt = server.invoketool("legginota", "nota1.md")
assert "Checklist sicurezza" in txt
def testsalvanota():
nome = "nuova_nota.md"
contenuto = "# Nuova\nTest"
resp = server.invoketool("salvanota", nome, contenuto)
assert "salvata" in resp
# pulizia
(server.BASE_PATH / nome).unlink()
PY
Esegui pytest -q per verificare che tutti i test passino.
Passo 9: client di prova
cat > client_prova.py <<'PY'
import sys
from mcp import Client
c = Client("http://127.0.0.1:8000")
print("Tool disponibili:", c.list_tools())
print("Resource disponibili:", c.list_resources())
print("Esempio listanote:", c.calltool("lista_note"))
PY
Esegui python client_prova.py per ispezionare il server.
Passo 10‑11: agente locale
cat > agent.py <<'PY'
import os
import json
import requests
from mcp import Client
Configura il client verso Ollama
OLLAMA_URL = "http://localhost:11434/v1/chat/completions"
MODEL = "qwen3.8-27b"
client = Client("http://127.0.0.1:8000")
def chiama_modello(messages):
payload = {
"model": MODEL,
"messages": messages,
"tools": [
{"type": "function", "function": {"name": "lista_note", "description": "Restituisce l'elenco delle note", "parameters": {"type": "object", "properties": {}}}},
{"type": "function", "function": {"name": "leggi_nota", "description": "Legge una nota", "parameters": {"type": "object", "properties": {"nome": {"type": "string"}}}}},
{"type": "function", "function": {"name": "salva_nota", "description": "Salva una nuova nota", "parameters": {"type": "object", "properties": {"nome": {"type": "string"}, "contenuto": {"type": "string"}}}}}
]
}
resp = requests.post(OLLAMA_URL, json=payload)
resp.raiseforstatus()
return resp.json()
def gestiscirisposta(assistantmsg):
if "toolcalls" in assistantmsg:
for call in assistantmsg["toolcalls"]:
name = call["function"]["name"]
args = json.loads(call["function"]["arguments"])
if name == "lista_note":
result = client.calltool("listanote")
elif name == "leggi_nota":
result = client.calltool("legginota", args["nome"])
elif name == "salva_nota":
result = client.calltool("salvanota", args["nome"], args["contenuto"])
else:
result = "Tool sconosciuto"
# invia risultato al modello
messages.append({"role": "tool", "name": name, "content": result})
return chiama_modello(messages)
else:
return assistant_msg
if _name == "main_":
# ciclo semplice: chiedi all’utente, chiama modello, gestisci tool
user_input = input("Cosa vuoi fare? ")
messages = [{"role": "user", "content": user_input}]
response = chiama_modello(messages)
final = gestisci_risposta(response["choices"][0]["message"])
print("Risposta finale:", final["choices"][0]["message"]["content"])
PY
L’agente invia la conversazione a Ollama, riceve eventuali tool_calls, le esegue tramite il client MCP e reinvia il risultato al modello. In questo modo il modello può leggere, creare o elencare note senza mai uscire dal dispositivo.
Conclusioni e prossimi sviluppi
Con meno di 75 minuti, un terminale e le dipendenze elencate, è possibile avere un agente AI locale che utilizza un MCP server stateless. La separazione netta tra server (che gestisce solo i dati) e agente (che gestisce il modello) rende l’architettura modulare, pronta a evolvere verso nuovi LLM o nuove risorse senza modificare il codice di base.
