Questo articolo non parla di MCP in sé, né di come implementare un protocollo JSON-RPC. Parla di quello che capita quando i tuoi vincoli architetturali reali scontrano le soluzioni “generiche” che il mercato ti offre. E di come, a volte, la decisione migliore è quella che nessuno avrebbe aspettato.
Private Mind gira offline, completamente. Il modello è Gemma in formato GGUF, caricato localmente con llama-cpp-python. L’applicazione promette una cosa semplice: scarica, unzippa, accendi. Non c’è cloud, non c’è rete, non c’è dipendenza da servizi esterni. È una promessa che vale quanto pesa.
Il problema arriva quando vuoi aggiungere un’integrazioni esterna, tipo connettere il tuo modello locale a Claude Code, o a un agente che sta girando da un’altra parte della macchina. MCP è il protocollo che lo fa. È il modo standard di dire: “Ehi, questo modello qui (o questo strumento, questa risorsa) sa parlare con te, e sa riconoscere i tuoi comandi.”
Ma quando guardi l’SDK ufficiale, quello che Anthropic mette a disposizione per chi vuole implementare un server MCP, scopri che non è leggero. L’SDK porta con sé Pydantic (che significa pydantic-core, cioè Rust compilato), WebSocket, Starlette, Uvicorn. È uno stack ASGI dentro un’app WSGI. È progettato per risolvere il problema generale “come gestisco questo protocollo in produzione?” e per farlo bene. Ma risolve anche il tuo, e il tuo non è affatto il suo caso d’uso più comune.
Ecco il vincolo invisibile: il progetto ha già tre estensioni compilate (lxml, cryptography, cffi). Aggiungerne altre tre non è un dettaglio, è una decisione di architettura. Ogni binario compilato è un punto in cui la build può fallire, è una riga che cambia fra Windows e Linux, è peso che sale all’interno di un pacchetto distribuito. Il progetto promette “scarica, unzippa, va”. Tre estensioni compilate invece di sei è la differenza fra una promessa tenuta e una promessa che si piega sotto il peso.
La spec come responsabilità
Il protocollo MCP è JSON-RPC 2.0. Sapere questo significa sapere quasi tutto: è vecchio, è semplice, ha una riga di storia lunga. Il sottinsieme che serve davvero è ancora più piccolo: initialize, tools/list, tools/call, ping, le notifiche che non ricevono risposta. Duecento righe di codice pulito. Niente Pydantic, niente Uvicorn, niente stack ASGI. Solo: ricevi un messaggio, interpreti, rispondi.
Il debito che questo porta con sé è esplicito e misurato: la spec va inseguita a mano. Se la versione di MCP cambia—dalla 2025-06-18 verso qualcosa di nuovo, nessuno vene a dirti “guarda, hai un errore”. Il tuo codice continua a funzionare fino a quando il primo client non manda qualcosa che non capisce. È il motivo per cui ci sono trentanove test che coprono solo la forma dei messaggi, non la logica. Uno inputSchema sbagliato nessuno te lo dice dal primo sguardo, il client che chiama il tool semplicemente non riceve la risposta che s’aspetta, e pensa che il server sia rotto.
Questo è il compromesso. La misura la decidi tu. La responsabilità è tua.
Il problema scoperto per strada
Una volta che il server MCP era a mezza strada, è emerso qualcosa che nessuno aveva considerato: il modello non è rientrante. L’oggetto Llama di llama-cpp-python non regge due create_chat_completion() che girano contemporaneamente sullo stesso oggetto. Condividono contesto, condividono cache, il risultato è incomprensibile o un crash. Non era un problema mentre c’era solo il browser, un utente, una finestra, un pulsante di invio disabilitato mentre genera. Ma MCP apre una seconda porta, un programma esterno che chiama mentre tu chatti è lo scenario normale, non il caso limite.
Qui è accaduto qualcosa di importante. Il problema era vero, ma non era nuovo, Flask gira con threaded=True da sempre. Il motivo per cui nessuno l’aveva visto è semplice, le architetture monolitiche piccole non forzano la concorrenza. Quando aggiungi un’integrazione esterna, la trovi.
La soluzione è un lock: prendi, esegui l’inferenza, rilascia. Non una coda invisibile, se il modello è occupato, rispondi subito “occupato”, perché una coda invisibile diventa un timeout del client, e un timeout del client è lo stesso problema con meno informazione. Chi arriva mentre il modello genera riceve un messaggio che lo dice, punto. È una linea che separa “il sistema sta funzionando” da “il sistema sta gridando di cosa sta facendo”.
Questo lock fa riconfigurare tutto. Non solo il server MCP, anche l’endpoint /api/chat del browser adesso dice “occupato” come evento SSE. La /api/chat e il tool ask di MCP facevano le stesse sette cose nello stesso ordine (controllo visione, caricamento agente, campionamento, espansione media, potatura, prompt di sistema, conteggio token). Duplicarle avrebbe voluto dire che una delle due, prima o poi, avrebbe smesso di fare una cosa senza che nessun test se ne accorgesse. Quindi quella logica è finita in un modulo condiviso, inference.py. Due porte d’ingresso, una logica.
Il framework: come pesare i trade-off
Valuta il debito tecnico che assumi se scegli di scrivere il codice a mano. Non è gratis. Ventiquattro pacchetti portano con sé ventiquattro mantenitori che guardano security, che rilasciano aggiornamenti, che pensano al tuo caso d’uso. Quando li scarti, quella responsabilità diventa tua. Non risolve il debito, lo trasforma. Misura cosa significa: per MCP, significa trentanove test sulla forma dei messaggi, significa leggere la spec ogni volta che cambia, significa svegliarsi la notte e dire “ma aspetta, una notifica malformata dovrebbe ricevere una risposta?” (la risposta è no, per la spec, ma l’istinto dice sì).
E ascolta quello che il progetto ti insegna quando scava più a fondo. Se scopri un problema per strada, un lock che manca, una configurazione che non si sa dove stia, quello è un segnale. Non è un bug. È il tuo design che ti dice “ehi, mi manca qualcosa.” Risolvilo non solo nel punto dove l’hai visto, ma riconsidera il disegno intorno. Quando aggiungi un lock all’inferenza, decidi anche dove quella logica vive, e chi può toccarla, e come lo comunichi a chi arriverà dopo di te.
I trade-off non sono fra “giusto” e “sbagliato”. Sono fra “coerente con quello che ho promesso” e “bello da un’altra prospettiva.” Scegli il coerente.
Se l’articolo ti è piaciuto restiamo in contatto su linkedin a: https://www.linkedin.com/in/andreatonin/
#ArchitetturaMicroservizi #TradeOffTecnici #VincoliReali #ProtocolliStandard #DipendenzeTecniche #DesignDecisions #OfflineFirst #ProblemiScopertiPerStrada #DebtoTecnico #Responsabilità
| Editorial Manager at Pills for Nerds | CTO & Technology Consultant |
Technology professional, editor and lifelong nerd with over 30 years of experience in the digital and innovation sectors. I work as a CTO and technology consultant, designing complex and scalable software ecosystems, and I am also active in IT education and professional training. Alongside my technology career, I serve as Editorial Manager and contributor at Pills for Nerds, online publication covering video games, game development, emerging technologies, artificial intelligence, digital culture and industry events. In my editorial role, I coordinate content planning and contribute articles, interviews, event reports and in-depth features. You can learn more about my consulting work at lucedigitale.com and connect with me on LinkedIn
















