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.

PrimaDal 28 luglio 2026
Handshake initialize e sessioniNessun handshake, nessuna sessione
Negoziazione inizialeScoperta tramite server/discover
Richieste avviate dal serverMulti Round‑Trip Requests (MRTR)
Notifiche di cambiamento sparseUnificate sotto subscriptions/listen
Tasks nel nucleo del protocolloTasks diventa un’estensione
Roots, Sampling, Logging, HTTP+SSEMarcati 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.

FileRuoloPasso
pyproject.tomlDipendenze e versione di Python1
dati/*.mdTre note di esempio2
server.pyMCP server con 4 tool, 1 resource, 1 prompt3, 5, 6, 7
test_server.pyCinque test in memoria con pytest8
client_prova.pyClient stdio per ispezionare il server9
agent.pyAgente con modello locale e ciclo di tool10, 11

Strumenti consigliati

    • Python: 3.15.1 consigliata, ammesse 3.14.9 e 3.13.17. Lo SDK richiede almeno 3.11.
    • SDK: mcp serie 2.x, installa con extra cli per mcp dev e mcp run.
    • uv: gestisce ambiente e dipendenze; in alternativa venv e pip.
    • Ollama: ultima versione stabile, espone endpoint compatibile con le API OpenAI su localhost:11434/v1.
    • Modello locale: con capability tools, ad esempio Qwen3.8-27B scaricato 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.