Quando l'assistente AI ha bisogno di leggere il tuo database, controllare un'email, lanciare un report o muoversi nel tuo cloud aziendale, non ti basta una chat: ti serve un'interfaccia tra il modello e i tuoi sistemi. Model Context Protocol (MCP), lo standard aperto lanciato da Anthropic nel novembre 2024 e adottato in 18 mesi da OpenAI, Google, Microsoft e Cursor, è quell'interfaccia. In questa guida costruiamo da zero un MCP server in Python che espone tre strumenti reali — lettura PostgreSQL, ricerca su file PDF locali, invio di Slack — e lo colleghiamo a Claude Desktop, ChatGPT e Cursor.

A chi serve questo tutorial e cosa otterrai

Ti serve se stai costruendo agenti AI per la tua azienda, se vuoi che Claude o ChatGPT abbiano accesso a dati che non sono pubblici sul web (CRM, ticket, log applicativi), o se vuoi capire come funziona davvero il tool use sotto il cofano. Alla fine avrai: un server MCP locale funzionante con tre tool, una resource esposta ai client, prompt riutilizzabili, e l'integrazione con tre client diversi.

Prerequisiti reali

  • Python 3.10+ installato (verifica con python --version).
  • Una shell (macOS, Linux o WSL su Windows; PowerShell funziona ma alcuni comandi cambiano).
  • Un editor: VS Code o Cursor sono ottimi.
  • Un client MCP almeno fra: Claude Desktop (gratis), ChatGPT (con piano Pro/Team e MCP attivato), Cursor (free tier sufficiente per test).
  • Per i tool d'esempio: PostgreSQL locale (oppure usa SQLite, ti spiego come switchare) e un workspace Slack di test con un bot token.

Quale SDK usare: ufficiale o FastMCP?

Ci sono due strade ufficialmente raccomandate da Anthropic.

  • MCP Python SDK: l'SDK ufficiale (modelcontextprotocol/python-sdk). Più verboso, controllo fine, indicato per casi enterprise complessi.
  • FastMCP: wrapper di alto livello (jlowin/fastmcp) ispirato a FastAPI. Decoratori semplici, sviluppo rapido. È oggi il modo più veloce per partire ed è ufficialmente supportato.

In questo tutorial usiamo FastMCP 2.x: stessa potenza, metà del codice.

Passo 1 — Setup del progetto

Crea una cartella, un ambiente virtuale e installa le dipendenze:

mkdir mcp-azienda && cd mcp-azienda
python -m venv .venv
source .venv/bin/activate   # su Windows: .venv\Scripts\activate
pip install fastmcp psycopg2-binary pypdf slack_sdk python-dotenv

Crea un file .env con le credenziali:

POSTGRES_URL=postgresql://utente:password@localhost:5432/mio_db
SLACK_BOT_TOKEN=xoxb-...
PDF_DIR=/Users/me/documenti

Passo 2 — Lo scheletro del server

Crea il file server.py:

from fastmcp import FastMCP
from dotenv import load_dotenv
import os
load_dotenv()

mcp = FastMCP("azienda-tools")

@mcp.tool()
def ping() -> str:
    """Ritorna pong, utile per verificare che il server funzioni."""
    return "pong"

if __name__ == "__main__":
    mcp.run()

Avvialo:

python server.py

Se vedi Starting MCP server «azienda-tools» via stdio sei a posto. Stdio è il transport più semplice: il server parla con il client tramite standard input/output. Esistono anche http e sse per server remoti, vedremo dopo come passare.

Passo 3 — Tool 1: leggere PostgreSQL

Aggiungi al file:

import psycopg2
from psycopg2.extras import RealDictCursor

@mcp.tool()
def query_database(sql: str, limit: int = 50) -> list[dict]:
    """Esegue una query SELECT (sola lettura) sul database aziendale.
    Per sicurezza vengono accettate solo query SELECT e viene applicato un LIMIT.
    """
    if not sql.strip().lower().startswith("select"):
        raise ValueError("Solo query SELECT sono permesse")
    if "limit" not in sql.lower():
        sql = sql.rstrip(";") + f" LIMIT {limit};"
    with psycopg2.connect(os.environ["POSTGRES_URL"]) as conn:
        with conn.cursor(cursor_factory=RealDictCursor) as cur:
            cur.execute(sql)
            return [dict(r) for r in cur.fetchall()]

Tre cose da notare. Primo: la docstring diventa la descrizione che il modello AI vede. Scrivila bene, perché influenza quando l'assistente decide di usare il tool. Secondo: il type hint sui parametri viene tradotto in schema JSON per il client. Terzo: una guardia di sicurezza elementare (solo SELECT, LIMIT obbligatorio) — gli MCP server hanno accesso reale ai dati, scrivi sempre come se l'altro lato fosse compromesso.

Passo 4 — Tool 2: cercare nei PDF

from pypdf import PdfReader
import pathlib

@mcp.tool()
def cerca_nei_pdf(testo: str, max_risultati: int = 5) -> list[dict]:
    """Cerca un'espressione nei PDF della cartella documenti aziendali.
    Ritorna nome file, pagina e estratto attorno al match.
    """
    risultati = []
    base = pathlib.Path(os.environ["PDF_DIR"])
    for pdf in base.glob("**/*.pdf"):
        try:
            reader = PdfReader(pdf)
            for i, page in enumerate(reader.pages, 1):
                t = page.extract_text() or ""
                idx = t.lower().find(testo.lower())
                if idx >= 0:
                    estratto = t[max(0, idx-100):idx+200]
                    risultati.append({"file": str(pdf.name), "pagina": i, "estratto": estratto})
                    if len(risultati) >= max_risultati:
                        return risultati
        except Exception:
            continue
    return risultati

Passo 5 — Tool 3: postare su Slack

from slack_sdk import WebClient

@mcp.tool()
def posta_slack(canale: str, messaggio: str) -> str:
    """Posta un messaggio in un canale Slack. Il canale va indicato come #nome."""
    client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
    res = client.chat_postMessage(channel=canale, text=messaggio)
    return f"Messaggio inviato (ts={res['ts']})"

Passo 6 — Una resource: il listino aziendale

Le risorse MCP sono file o dati che il client può allegare alla conversazione. Aggiungi:

@mcp.resource("file://listino.csv")
def listino() -> str:
    """Il listino prezzi 2026 in CSV."""
    return open("./dati/listino.csv").read()

Anche un prompt riutilizzabile:

@mcp.prompt()
def analizza_ticket(testo_ticket: str) -> str:
    return f"Sei un analista di supporto. Classifica per priorità (alta/media/bassa) e categoria questo ticket: {testo_ticket}"

Passo 7 — Collegare Claude Desktop

Apri il file di configurazione di Claude Desktop:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Aggiungi:

{
  "mcpServers": {
    "azienda-tools": {
      "command": "/Users/me/mcp-azienda/.venv/bin/python",
      "args": ["/Users/me/mcp-azienda/server.py"]
    }
  }
}

Riavvia Claude Desktop. Nella barra in basso compare l'icona MCP con i tool elencati. Prova a chiedere: «Cerca nei PDF se compare la parola fattura e poi posta un riassunto su #generale».

Passo 8 — Collegare ChatGPT e Cursor

Per ChatGPT (Pro/Team/Enterprise) vai su Settings → Connectors e aggiungi un Custom MCP Server, scegliendo il transport HTTP. Per esporre il tuo server in HTTP cambia l'avvio:

mcp.run(transport="http", host="127.0.0.1", port=8765)

E indica a ChatGPT l'URL http://127.0.0.1:8765. Per accessi da remoto usa un tunnel sicuro (ngrok, Cloudflare Tunnel) e abilita un'API key tramite l'hook di autenticazione di FastMCP.

Per Cursor: apri Settings → MCP, clicca «Add new MCP Server» e incolla la stessa configurazione di Claude Desktop. Cursor è particolarmente utile per debug: mostra ogni request/response JSON-RPC.

Con MCP gli stessi tool funzionano in Claude Desktop, ChatGPT e Cursor. Foto Pexels.

Errori comuni e come risolverli

  • «Server not responding» in Claude Desktop: il path del Python è sbagliato. Usa il path assoluto dell'interpreter del tuo venv, non python.
  • Tool non chiamato: la docstring è troppo vaga. Riscrivila in italiano spiegando quando il modello dovrebbe usarlo («Usa questo tool quando l'utente chiede di...»).
  • «Tool call failed: validation error»: i parametri del modello non rispettano lo schema. Aggiungi type hint più espliciti (es. Literal["high","med","low"]) e usa Field di Pydantic per i vincoli.
  • Risposte JSON troppo grandi: i client MCP hanno un limite. Pagina o tronca i risultati e ritorna anche un campo «has_more».

Sicurezza, varianti e prossimi passi

L'MCP non è un endpoint pubblico per default ma se lo esponi su HTTP devi pensare a tre cose. Autenticazione: FastMCP supporta middleware bearer-token. Permessi per tool: separa un MCP a sola lettura da uno con write. Logging: registra ogni chiamata, con utente e parametri, per audit. Esistono già soluzioni open-source come modelcontextprotocol/registry per gestire più server in un'organizzazione.

Quando NON usare un MCP server: se l'assistente AI deve fare cose semplici e già pubbliche sul web, basta un browser tool come quello incluso in Claude. Se invece i dati sono solo nel tuo gestionale, nei tuoi PDF o nei tuoi database, MCP è il modo pulito per dargli accesso, mantenendo però controllo su cosa succede a ogni chiamata. Una buona prossima tappa è aggiungere OAuth2 per Slack e Microsoft Graph, oppure costruire un MCP che parla con n8n e attiva flussi già configurati. La specifica MCP è pubblica e in evoluzione: vale la pena tenerla sul comodino.