Hai mai desiderato che un'intelligenza artificiale potesse interrogare direttamente i dati della tua azienda? Con MCP (Model Context Protocol) questo Γ¨ possibile. In questa guida ti racconto come abbiamo costruito un server MCP reale, collegandolo a un CRM con oltre 1.800 clienti e 5.400 ordini.
Cos'Γ¨ MCP e perchΓ© dovresti conoscerlo
MCP Γ¨ un protocollo aperto creato da Anthropic che standardizza il modo in cui le applicazioni AI comunicano con fonti di dati esterne. Pensalo come una "USB-C per l'AI": un'interfaccia universale che permette a qualsiasi modello linguistico di connettersi a qualsiasi servizio.
Prima di MCP, collegare un LLM a 5 servizi diversi significava scrivere 5 integrazioni custom. Con 3 modelli AI, il lavoro si moltiplicava per 3. Un problema MΓN. MCP lo trasforma in M+N: scrivi un server MCP una volta, funziona con qualsiasi client compatibile.
I tre concetti fondamentali
MCP si basa su tre primitivi:
π§ Tools
Funzioni che l'AI puΓ² invocare. Sono il cuore di MCP. L'AI decide autonomamente quando chiamarle, basandosi sul nome e sulla descrizione.
Esempio: cerca_cliente("Rossi")
π Resources
Dati che l'AI puΓ² leggere. Simili a endpoint GET. L'applicazione host decide quando accedervi.
Esempio: configurazione azienda, listino prezzi
π¬ Prompts
Template riutilizzabili che l'utente seleziona esplicitamente per workflow ricorrenti.
Esempio: "Analisi completa cliente", "Report giornaliero"
Come funziona: il flusso completo
Quando un utente scrive una domanda nella chat AI, ecco cosa succede dietro le quinte:
β
Chat Widget β invia la domanda al backend
β
Backend (Laravel) β recupera i tool MCP disponibili
β
Backend β invia domanda + tool a OpenAI
β
OpenAI β decide: "Devo chiamare
top_clienti"
β
Backend β esegue il tool sul Server MCP
β
Server MCP β query MySQL β ritorna i dati JSON
β
Backend β rimanda i dati a OpenAI
β
OpenAI β formula la risposta in italiano
β
Utente: "I tuoi 3 migliori clienti sono: 1. Mario Rossi (β¬12.450)..."
La cosa fondamentale: OpenAI non parla direttamente col server MCP. Γ il backend che fa da intermediario β riceve le richieste di tool call da OpenAI, le esegue sul server MCP e rimanda i risultati.
L'architettura del nostro progetto
Abbiamo collegato un CRM reale (Snapzoid) a un assistente AI. Ecco lo stack:
| Componente | Tecnologia | Porta / URL |
|---|---|---|
| Frontend Chat | jQuery + CSS | Embedded nel tema |
| Backend API | Laravel (PHP) | /api/{token}/ai/ask |
| AI Engine | OpenAI GPT-4o | API esterna |
| MCP Proxy | Python FastAPI | proxy-mcp.snapzoid.com:8006 |
| MCP Server | Python FastMCP | mcp.snapzoid.com:8005 |
| Database | MySQL (OVH CloudDB) | Porta 35735 |
Il problema PHP + MCP (e come lo abbiamo risolto)
Il protocollo MCP via SSE (Server-Sent Events) Γ¨ asincrono: il client mantiene una connessione long-lived e scambia messaggi in parallelo. PHP Γ¨ sincrono β ogni richiesta segue un flusso lineare request β response. Non puΓ² gestire SSE.
La soluzione? Un proxy Python che traduce semplici chiamate REST (che Laravel sa fare perfettamente) in sessioni MCP asincrone:
β POST /tools/list β REST semplice
β POST /tools/call β REST semplice
βΌ
Proxy Python (FastAPI, asincrono, porta 8006)
β Apre sessione SSE β invia JSON-RPC β attende risposta
βΌ
Server MCP (FastMCP, porta 8005)
β
βΌ
Database MySQL
Il Server MCP in Python
Il server espone 4 tool al modello AI. Ecco la struttura di un tool:
from mcp.server.fastmcp import FastMCP
from pydantic import Field
mcp = FastMCP("Snapzoid CRM", host="0.0.0.0", port=8005)
@mcp.tool()
def cerca_clienti(
snztoken: str = Field(description="Token autenticazione"),
query: str = Field(description="Testo da cercare"),
limit: int = Field(default=10, description="Max risultati")
) -> str:
"""Cerca clienti nel CRM per nome, email, telefono o CF.
Usa questo tool quando l'utente chiede di trovare un contatto."""
user_id = get_user_id(snztoken)
risultati = query_db(
"SELECT * FROM customer WHERE user_id = %s AND name LIKE %s",
(user_id, f"%{query}%")
)
return json.dumps({"risultati": risultati})
if __name__ == "__main__":
mcp.run(transport="sse")
Punti chiave da notare:
- La docstring non Γ¨ solo documentazione β Γ¨ l'istruzione che l'AI legge per capire quando usare il tool
- I
Field(description=...)descrivono ogni parametro all'AI - Il parametro
snztokengarantisce l'isolamento multi-tenant (ogni utente vede solo i suoi dati) - Il server usa un connection pool MySQL (5 connessioni) per le performance
I 4 tool esposti
| Tool | Cosa fa |
|---|---|
cerca_clienti |
Ricerca LIKE su nome, cognome, email, telefono, ragione sociale, CF, P.IVA |
info_cliente |
Dettaglio completo: anagrafica, statistiche, top 5 prodotti, ultimi ordini, tag, note |
top_clienti |
Classifica migliori clienti per spesa in un periodo configurabile |
statistiche_generali |
Dashboard: totale clienti, ordini, fatturato, dati ultimi 30 giorni |
Il Proxy: il ponte tra PHP e MCP
Il proxy ha due compiti fondamentali:
π Nascondere il token
Quando restituisce la lista tool a Laravel, rimuove snztoken dai parametri. Così OpenAI non lo vede e non cerca di valorizzarlo.
π Iniettare il token
Quando Laravel chiama un tool, il proxy prende il token dall'header X-Snz-Token e lo aggiunge automaticamente ai parametri della chiamata.
L'integrazione Laravel + OpenAI
Il backend Laravel orchestra tutto con un loop tool-call:
// Pseudocodice del loop
$tools = POST proxy/tools/list; // Recupera tool MCP
$tools = convertiPerOpenAI($tools); // Adatta il formato
$response = OpenAI($messaggio, $tools); // Prima chiamata
while ($response ha tool_calls) {
foreach ($response->tool_calls as $call) {
$result = POST proxy/tools/call // Esegui via MCP
$messages[] = tool_result($result);
}
$response = OpenAI($messages, $tools); // OpenAI elabora
}
return $response->content; // Risposta finale in italiano
_ (rifiutati), array vuoti [] (non accettati come properties), e campi title/description nello schema root (non supportati).Infrastruttura e deployment
Tutto gira su un singolo VPS Contabo con Ubuntu, dietro Nginx reverse proxy e certificati Let's Encrypt:
- mcp.snapzoid.com (porta 8005) β Server MCP con configurazione speciale per SSE: buffering disabilitato, timeout 24h
- proxy-mcp.snapzoid.com (porta 8006) β Proxy REST, timeout standard 60s
- Entrambi gestiti da systemd con auto-restart (max 5 tentativi in 60 secondi)
- Python 3.11 compilato da sorgente (Ubuntu 20.04 ha solo Python 3.8)
Come testare un server MCP
Prima di collegare tutto, Γ¨ fondamentale testare il server MCP isolatamente:
1. MCP Inspector (GUI)
npx @modelcontextprotocol/inspector
# Inserisci: https://mcp.snapzoid.com/sse?snz-token=TOKEN
Tool visuale di Anthropic: mostra i tool disponibili, permette di chiamarli e ispezionare le risposte.
2. Curl (terminale)
curl -X POST https://proxy-mcp.snapzoid.com/tools/call \
-H "Content-Type: application/json" \
-H "X-Snz-Token: IL_TUO_TOKEN" \
-d '{"name":"statistiche_generali","arguments":{}}'
3. Test full stack
curl "https://app.snapzoid.com/api/TOKEN/ai/ask?q=quanti+clienti+ho"
Le lezioni imparate
Ogni progetto reale ha i suoi ostacoli. Ecco quelli che abbiamo incontrato e risolto:
| Problema | Causa | Soluzione |
|---|---|---|
| Python 3.8 troppo vecchio | Ubuntu 20.04 default | Compilato Python 3.11 da sorgente |
| PHP non puΓ² fare SSE | Protocollo asincrono | Proxy Python come ponte REST β SSE |
| Sessioni utente mescolate | ContextVar non isola in async | Parametro esplicito snztoken in ogni tool |
OpenAI rifiuta _token |
Prefisso _ non ammesso |
Rinominato in snztoken |
| Schema vuoti rifiutati | OpenAI non accetta [] |
Fix: [] β {}, required vuoti rimossi |
| Risposta MCP con formato misto | A volte oggetti, a volte dict | Funzione extract_content() multi-formato |
I risultati
1.860+
clienti accessibili
5.431+
ordini queryabili
~3 sec
tempo di risposta
4
tool esposti
Conclusione
MCP non Γ¨ solo un protocollo β Γ¨ un cambio di paradigma nel modo in cui colleghiamo AI e dati aziendali. Con un investimento relativamente contenuto (un server Python, un proxy e qualche riga di Laravel) puoi dare ai tuoi utenti un assistente AI che interroga dati reali in linguaggio naturale.
I punti chiave da ricordare:
- MCP Γ¨ language-agnostic β il server puΓ² essere in Python, il backend in PHP, il frontend in qualsiasi cosa
- Le docstring sono fondamentali β sono le istruzioni che l'AI legge per decidere quando usare un tool
- Il proxy Γ¨ necessario con PHP β ma aggiunge anche vantaggi come l'iniezione automatica del token
- Testa sempre in isolamento β MCP Inspector e curl prima di collegare tutto
- L'isolamento multi-tenant Γ¨ critico β mai usare stato globale, sempre parametri espliciti
Il protocollo Γ¨ giovane e in evoluzione (il nuovo Streamable HTTP semplifica ulteriormente il deployment), ma Γ¨ giΓ pronto per la produzione. Se lavori con PMI e vuoi differenziarti, un assistente AI collegato ai dati reali del cliente Γ¨ un valore aggiunto enorme.
π Vuoi Risultati Come Questi?
Posso aiutarti a trasformare il tuo business digitale con soluzioni su misura.
Richiedi Consulenza Gratuita