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.

In parole semplici: un server MCP Γ¨ un servizio che espone delle "funzioni" che l'AI puΓ² leggere, capire e chiamare autonomamente quando servono. L'AI legge i nomi delle funzioni e le loro descrizioni per decidere quando usarle.

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:

Utente: "Chi sono i miei migliori clienti?"
  β†“
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:

Laravel (PHP, sincrono)
  β”‚ 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 snztoken garantisce 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
⚠️ Attenzione: OpenAI è molto rigido sulla validazione degli schema. Abbiamo dovuto correggere: parametri con prefisso _ (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