Il Model Context Protocol (MCP) è la specifica aperta con cui gli LLM, in particolare i client agentici come Claude Code, accedono in modo standard a tool e dati esterni. Diversamente dal classico function calling proprietario, MCP separa nettamente il client (l’agente AI) dal server (l’integrazione), permettendo di riusare lo stesso server su client diversi e di esporre nuove capability senza toccare il modello.
Per un quadro architetturale e strategico più ampio rimando a MCP Server: l’architettura silente del Web intelligente, mentre per un caso d’uso verticale già implementato vale la pena leggere l’audit dei dati strutturati con Claude Code e server MCP Schema.org. Questo articolo si concentra sulla parte operativa che entrambi non coprono: come si installa un server MCP esistente in Claude Code, come si configura, come se ne scrive uno da zero, partendo da due esempi concreti — DataForSEO per i dati SERP e un server custom Python per Google Analytics 4.
Cosa risolve MCP rispetto a tool use, plugin e function calling
Prima di MCP esistevano tre famiglie di integrazione fra LLM e mondo esterno: il function calling proprietario di ciascun vendor (OpenAI, Anthropic, Gemini, ognuno con il proprio schema JSON), i plugin ChatGPT (specifici di un client), e gli SDK applicativi che mascheravano tutto dietro orchestrazioni custom. Ogni integrazione era riscritta per ogni LLM, ogni piattaforma, ogni runtime.
MCP, rilasciato da Anthropic a fine 2024 come specifica aperta basata su JSON-RPC 2.0, normalizza il contratto fra le due parti. Il server espone tre primitive — tools (funzioni eseguibili), resources (dati leggibili) e prompts (template riutilizzabili) — e qualunque client conforme può scoprirle dinamicamente via handshake. La conseguenza pratica è che lo stesso server DataForSEO funziona oggi su Claude Code, sull’app desktop Claude, su Cursor, su Continue, su Zed e su tutti i nuovi client che adotteranno la specifica.
| Aspetto | Function calling (OpenAI/Anthropic) | Plugin ChatGPT | MCP |
|---|---|---|---|
| Standard aperto | No, schema per vendor | No, deprecato | Sì, JSON-RPC 2.0 |
| Trasporto | HTTP del vendor | HTTPS pubblico | stdio, SSE, Streamable HTTP |
| Riusabilità cross-client | Bassa | Solo ChatGPT | Alta (ogni client conforme) |
| Primitive esposte | Funzioni | OpenAPI endpoints | Tools + Resources + Prompts |
| Discovery dinamica | Parziale | Manifest statico | Sì, via handshake |
| Esecuzione locale | No | No | Sì, server stdio |
Il vantaggio operativo per chi lavora in CLI è enorme: anziché scrivere uno script Python che chiama l’API DataForSEO, lo passa a un wrapper che chiama l’API di Claude, e gestisce a mano l’iterazione dei tool, basta dichiarare il server una volta in .mcp.json e Claude Code lo orchestra autonomamente all’interno della conversazione. Lo stesso ragionamento sviluppato per il workflow CLI in fare SEO da terminale si applica qui in scala superiore.
Come funziona un server MCP: host, client, server, transport
La spec MCP definisce quattro componenti distinti che è utile non confondere:
- Host: l’applicazione che l’utente lancia (Claude Code, Claude Desktop, Cursor). Gestisce la sessione, l’autorizzazione e la UI.
- Client: il componente interno all’host che parla un singolo server MCP. Un host può avere N client, uno per server.
- Server: il processo che espone tools/resources/prompts. Può essere un binario locale, uno script Python, un container, o un endpoint HTTP remoto.
- Transport: il canale fisico fra client e server. La spec attuale prevede
stdio(processo figlio, default per server locali),sse(deprecato dal 2025-03-26) estreamable-http(per server remoti).
Tutto il dialogo viaggia in JSON-RPC 2.0. L’handshake iniziale è initialize → initialized, dopo il quale il client invia tools/list per scoprire le funzioni disponibili e tools/call per eseguirle. Resources e prompts seguono lo stesso pattern (resources/list, resources/read, prompts/list, prompts/get). Per debug, attivando il flag --mcp-debug in Claude Code è possibile ispezionare ogni messaggio in chiaro.

Installare Claude Code e configurare il primo server MCP
Claude Code è la CLI ufficiale di Anthropic per l’uso agentico di Claude in un terminale. L’installazione richiede Node.js 18+: Per chi vuole capire dove esattamente la CLI conserva configurazione MCP, sessioni e memoria persistente, ho mappato il filesystem e memoria di Claude Code in un articolo dedicato.
# Installazione globale
npm install -g @anthropic-ai/claude-code
# Verifica
claude --version
# Login (apre il browser per OAuth)
claude
Per aggiungere un server MCP esistono tre scope mutuamente esclusivi, con regole di precedenza ben precise:
local(default): valido solo per il progetto corrente e per l’utente che lo aggiunge. Salvato in~/.claude.jsondentro il record del progetto. Ideale per credenziali personali.project: scritto in.mcp.jsonnella root del progetto. Versionabile in Git, condivisibile col team. Mai mettere secrets qui.user: globale per tutti i progetti dell’utente. Salvato in~/.claude.jsona livello utente. Adatto a server generici (Filesystem, Git).
La sintassi base di claude mcp add accetta nome, comando da eseguire, eventuali argomenti, variabili d’ambiente e il tipo di trasporto:
# Server stdio locale (un eseguibile sul tuo sistema)
claude mcp add filesystem \
--scope user \
-- npx -y @modelcontextprotocol/server-filesystem /var/www
# Server con variabili d'ambiente
claude mcp add github \
--scope project \
--env GITHUB_TOKEN=ghp_xxx \
-- npx -y @modelcontextprotocol/server-github
# Server remoto su Streamable HTTP
claude mcp add my-remote \
--transport http \
--scope user \
https://mcp.example.com/v1
# Listare i server registrati
claude mcp list
# Rimuovere un server
claude mcp remove filesystem
Quando uno scope è project, l’output è un file .mcp.json in formato standard, lo stesso usato da Claude Desktop e da Cursor. Questo è il formato che ogni client MCP-compatibile è in grado di leggere:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxx"
}
},
"ga4": {
"command": "python",
"args": ["-m", "ga4_mcp_server"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/secrets/sa.json",
"GA4_PROPERTY_ID": "123456789"
}
}
}
}
Nota: dentro un progetto versionato è prassi committare .mcp.json con i campi env svuotati o con placeholder, e gestire i secret reali via .env ignorato da Git. Vedremo nella sezione sicurezza come automatizzare l’iniezione.
Esempio 1 — DataForSEO MCP per estrarre dati SERP da Claude Code
DataForSEO distribuisce un server MCP ufficiale che espone come tools le sue principali API (SERP, Keywords Data, DataForSEO Labs, Backlinks, On-Page, Content Analysis, AI Optimization). Una volta registrato in Claude Code, è possibile chiedere a Claude in linguaggio naturale di estrarre dati SERP, suggerire keyword o analizzare un profilo backlink, senza scrivere codice di integrazione.
Prerequisiti: account DataForSEO attivo, login e password API (non quelli del backoffice), Node.js 18+ per eseguire il server via npx.
# Registrazione del server MCP DataForSEO in Claude Code
claude mcp add dataforseo \
--scope user \
--env [email protected] \
--env DATAFORSEO_PASSWORD=api_password_qui \
-- npx -y @dataforseo/mcp-server-typescript
# Verifica handshake e tools list
claude mcp list
claude --mcp-debug
Aperta una sessione di Claude Code in una qualunque directory di lavoro, il server espone decine di tools. Un esempio di prompt operativo che fa lavorare tre tools in catena:
# Prompt all'agente
> Estrai la top 20 SERP organica italiana per la query
"audit dati strutturati", poi prendi le prime 5 URL e dimmi
per ciascuna le 10 keyword posizionate con più traffico
stimato. Salva il risultato in serp_audit.csv.
# Claude Code orchestra automaticamente:
# 1. dataforseo:serp_organic_live_advanced
# (location_code: 2380 - Italy, language_code: it, depth: 20)
# 2. dataforseo:dataforseo_labs_google_ranked_keywords
# (in loop sulle 5 URL estratte)
# 3. Write tool → serp_audit.csv
Lo stesso pattern si applica a query più complesse: analisi del gap keyword fra due competitor (dataforseo_labs_google_domain_intersection), bulk traffic estimation per validare una lista di URL, audit on-page Lighthouse di una landing prima della pubblicazione (on_page_lighthouse). Il valore non è eseguire una singola chiamata API, ma poterle concatenare in autonomia in base agli output intermedi, esattamente come faresti in una pipeline Python ma senza scrivere la pipeline. Per un esempio più strutturato di pipeline keyword data-driven che combina DataForSEO con altre fonti vedi costruire una lista di keyword per Google Ads con GSC, Ads API, DataForSEO ed embeddings.
Esempio 2 — Server MCP custom in Python per Google Analytics 4
Quando un server MCP per il dato che ti serve non esiste — o esiste ma è troppo generico — scrivere il proprio richiede un’ora. L’SDK Python ufficiale (pip install mcp) include il modulo FastMCP, che riduce la registrazione di un tool a un decoratore @mcp.tool() su una funzione Python. Vediamo come esporre le metriche GA4 più richieste lato SEO: top pagine per pageviews, traffico per source/medium e utenti in tempo reale.
Setup credenziali GA4
L’autenticazione preferita per uso server-side è un Service Account con ruolo Viewer sulla proprietà GA4 di interesse. La procedura:
- In Google Cloud Console crea un progetto e abilita la Google Analytics Data API.
- Crea un Service Account e scarica la chiave JSON.
- In GA4 Admin → Property Access Management aggiungi l’email del Service Account con ruolo Viewer.
- Recupera il
PROPERTY_IDnumerico (Admin → Property Settings).
Codice del server
Salva il file come ga4_mcp_server.py. Il server espone tre tools, ognuno con type hints che FastMCP traduce automaticamente in JSON Schema verso il client:
"""Server MCP minimale per Google Analytics 4.
Esporta tre tool: top_pages, traffic_by_source, realtime_active_users.
Auth: Service Account via GOOGLE_APPLICATION_CREDENTIALS.
Proprietà target: GA4_PROPERTY_ID (env).
"""
import os
from datetime import date, timedelta
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
DateRange, Dimension, Metric, RunReportRequest, RunRealtimeReportRequest,
)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("ga4")
PROPERTY_ID = os.environ["GA4_PROPERTY_ID"]
client = BetaAnalyticsDataClient() # legge GOOGLE_APPLICATION_CREDENTIALS
def _date_range(days: int) -> DateRange:
end = date.today() - timedelta(days=1) # GA4 ha ~24h di latenza
start = end - timedelta(days=days - 1)
return DateRange(start_date=start.isoformat(), end_date=end.isoformat())
@mcp.tool()
def top_pages(days: int = 28, limit: int = 25) -> list[dict]:
"""Restituisce le pagine con più screenPageViews negli ultimi N giorni."""
req = RunReportRequest(
property=f"properties/{PROPERTY_ID}",
date_ranges=[_date_range(days)],
dimensions=[Dimension(name="pagePath"), Dimension(name="pageTitle")],
metrics=[Metric(name="screenPageViews"),
Metric(name="totalUsers"),
Metric(name="averageSessionDuration")],
limit=limit,
)
resp = client.run_report(req)
return [
{
"path": r.dimension_values[0].value,
"title": r.dimension_values[1].value,
"views": int(r.metric_values[0].value),
"users": int(r.metric_values[1].value),
"avg_session_sec": float(r.metric_values[2].value),
}
for r in resp.rows
]
@mcp.tool()
def traffic_by_source(days: int = 28) -> list[dict]:
"""Sessioni per source/medium negli ultimi N giorni."""
req = RunReportRequest(
property=f"properties/{PROPERTY_ID}",
date_ranges=[_date_range(days)],
dimensions=[Dimension(name="sessionSource"),
Dimension(name="sessionMedium")],
metrics=[Metric(name="sessions"),
Metric(name="engagedSessions"),
Metric(name="conversions")],
limit=100,
)
resp = client.run_report(req)
return [
{
"source": r.dimension_values[0].value,
"medium": r.dimension_values[1].value,
"sessions": int(r.metric_values[0].value),
"engaged": int(r.metric_values[1].value),
"conversions": float(r.metric_values[2].value),
}
for r in resp.rows
]
@mcp.tool()
def realtime_active_users() -> dict:
"""Utenti attivi negli ultimi 30 minuti, segmentati per Paese."""
req = RunRealtimeReportRequest(
property=f"properties/{PROPERTY_ID}",
dimensions=[Dimension(name="country")],
metrics=[Metric(name="activeUsers")],
limit=50,
)
resp = client.run_realtime_report(req)
by_country = {r.dimension_values[0].value: int(r.metric_values[0].value)
for r in resp.rows}
return {"total": sum(by_country.values()), "by_country": by_country}
if __name__ == "__main__":
mcp.run() # trasporto stdio di default
Dipendenze e registrazione
# requirements.txt
mcp>=1.2.0
google-analytics-data>=0.18.0
python -m venv venv
source venv/bin/activate # su Windows: venv\Scripts\activate
pip install -r requirements.txt
# Test rapido del server (deve stampare il banner JSON-RPC e bloccarsi su stdin)
GA4_PROPERTY_ID=123456789 \
GOOGLE_APPLICATION_CREDENTIALS=/secrets/sa.json \
python ga4_mcp_server.py
# Registrazione in Claude Code, scope user
claude mcp add ga4 \
--scope user \
--env GA4_PROPERTY_ID=123456789 \
--env GOOGLE_APPLICATION_CREDENTIALS=/secrets/sa.json \
-- /path/to/venv/bin/python /path/to/ga4_mcp_server.py
Da questo momento ogni nuova sessione di Claude Code ha accesso ai tre tool. Esempio di interazione reale:
> Dammi le 15 pagine con più views negli ultimi 30 giorni,
poi per ciascuna recupera in DataForSEO le keyword
posizionate in top 10 e generami un CSV con
pagina, views, top_keyword, posizione, search_volume.
[Claude Code esegue in sequenza]
ga4:top_pages(days=30, limit=15)
→ 15 righe
dataforseo:dataforseo_labs_google_ranked_keywords(target=url_1, filters=...)
→ 12 keyword
... (loop)
Write tool → seo_priority.csv
Per chi vuole estendere il server in ottica di reporting AI-driven c’è ampio margine: aggiungere un tool compare_periods che restituisce delta percentuali fra due range, oppure un tool landing_pages_decline che incrocia GA4 e GSC per identificare pagine in calo organico. L’integrazione naturale è con i contenuti analizzati in come visualizzare il traffico referral dalle piattaforme AI in GA4: con un tool dedicato Claude può estrarre la quota di referral ChatGPT/Perplexity/Claude e correlarla all’andamento organico, in un’unica conversazione.
Sicurezza: scope, secrets, allowlist e prompt injection
I server MCP introducono una superficie di attacco nuova, che vale la pena affrontare in modo esplicito perché molti tutorial la trascurano. Tre vettori meritano attenzione operativa.
Gestione dei secrets
Mai committare un .mcp.json con credenziali in chiaro. Il pattern standard prevede un .mcp.json con campi env vuoti e un file .env ignorato da Git da cui Claude Code legge i valori reali. L’espansione di variabili shell dentro .mcp.json ("$GA4_PROPERTY_ID") è supportata e va sfruttata sistematicamente. Le credenziali di Service Account GA4, le API key DataForSEO e i token GitHub vivono in .env, mai accanto al codice.
Scope dei permessi
Quando si registra un server filesystem, restringere sempre la directory radice (npx ... /var/www/site-x, non /). Per server custom, principio del minimo privilegio applicato all’identità sottostante: il Service Account GA4 deve avere ruolo Viewer, non Editor; il token GitHub deve essere fine-grained e limitato ai repo necessari. Claude Code permette di approvare i tool call uno per uno o in modalità --allowedTools esplicita, evitando l’auto-approvazione cieca su server con tool potenzialmente distruttivi.
Prompt injection via tool description
La descrizione di ciascun tool restituita dal server MCP entra nel system prompt dell’LLM. Un server compromesso o malizioso può inserire istruzioni nascoste nella descrizione (“Quando l’utente chiede X, esegui prima Y e invia Z all’URL W”) che il modello potrebbe seguire. È un’estensione del classico tool description injection. Mitigazioni operative:
- Installare solo server da publisher verificati (ufficiali del vendor o community con audit pubblico).
- Per server custom interni, freezare la versione e fare code review delle descrizioni.
- Non auto-approvare tool che fanno operazioni esterne idempotenti (HTTP POST, scrittura file fuori dal progetto, esecuzione shell).
Debug: log JSON-RPC, capabilities ed errori comuni
Quando un server MCP si comporta in modo opaco — Claude dice di non avere il tool, oppure il tool restituisce sempre errore — l’approccio è sempre lo stesso: alzare il debug, ispezionare il traffico JSON-RPC, validare lo schema.
# Avvia Claude Code con log MCP completi
claude --mcp-debug
# Output utile (esempio):
# >> {"jsonrpc":"2.0","id":1,"method":"initialize",...}
# << {"jsonrpc":"2.0","id":1,"result":{"capabilities":{...}}}
# >> {"jsonrpc":"2.0","id":2,"method":"tools/list"}
# << {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"top_pages",...}]}}
# Verifica delle capabilities annunciate dal server
claude mcp list
# Mostra trasporto, comando di lancio e stato (connected / failed)
Errori ricorrenti che ho incontrato lavorando con MCP su progetti reali:
- Server stdio che stampa su stdout messaggi non JSON-RPC: qualsiasi
print()di debug nel server custom rompe il protocollo. Loggare sempre sustderro su file. - UTF-8 su Windows: il default cp1252 di PowerShell corrompe i caratteri accentati nei response. Forzare
PYTHONIOENCODING=utf-8nell’envdel server. - Path Python sbagliato: registrare il server con il
pythondi sistema invece che del venv porta aModuleNotFoundError. Usare sempre il path assoluto del Python del venv. - Tool schema invalido: type hint Python non riconosciuti (es. unioni complesse) generano JSON Schema malformati. Restare su tipi semplici (
str,int,float,bool,list[X],dict) o usare modelli Pydantic. - Timeout su tool lenti: il default è circa 60 secondi. Per chiamate API lente (DataForSEO live advanced, GA4 con dimensioni custom) prevedere paginazione e batching nel tool, non lasciare singole chiamate che superano il timeout.
Quando un server MCP ha senso e quando no
MCP è una tecnologia abilitante, non una scelta architetturale neutra. La domanda da porsi prima di scrivere o installare un server è semplice: chi e cosa ci parlerà?
| Scenario | Approccio consigliato |
|---|---|
| Integrazione one-shot, batch notturno, output deterministico | Script Python diretto, niente MCP |
| Tool usato in modo conversazionale, decisioni intermedie sui dati | MCP server |
| Stessa fonte dato consumata da Claude Code, Cursor, Claude Desktop | MCP server (riuso cross-client) |
| Workflow ripetitivo con N step in catena, ognuno con parametri variabili | MCP server + sub-agenti, vedi pipeline Schema.org |
| Singola query SQL/HTTP senza branching | Slash command o script, non MCP |
| Logica complessa di orchestrazione, errori da gestire a monte | Pipeline applicativa con SDK Anthropic, MCP non aggiunge valore |
La trappola tipica è scrivere un server MCP per una funzione che verrà invocata sempre nello stesso modo, con gli stessi parametri: in quel caso il prezzo del round-trip JSON-RPC e dell’orchestrazione dell’LLM è puro overhead rispetto a un comando bash. Inversamente, sottovalutare MCP per workflow esplorativi — dove il valore è proprio nella concatenazione adattiva di chiamate — significa restare bloccati in script monolitici che ogni tre giorni vanno riscritti.
Per chi sta valutando quanto il proprio sito sia tecnicamente pronto a essere consumato da agenti AI, MCP è solo un tassello: server-side rendering, dati strutturati corretti, accessibilità degli agenti restano prerequisiti — il tema è discusso in come verificare se un sito è pronto per l’AI. MCP è la parte del puzzle che permette agli LLM di agire; il resto del puzzle è permettere loro di capire.
Risorse di riferimento: modelcontextprotocol.io (specifica ufficiale), documentazione Claude Code, repository dei reference server, Google Analytics Data API v1, DataForSEO API docs.
Articoli correlati
Autore
Mi chiamo Giovanni Sacheli e dal 2009 aiuto le aziende a farsi trovare online. Sono specializzato in SEO tecnica e PPC, competenze che applico quotidianamente nella mia agenzia, Searcus Swiss Sagl. Mi piace sviluppare strumenti a supporto del mio lavoro, ho creato SEOdata.app e cluster.army e co-scritto il libro SEO Audit Avanzato. Curo maniacalmente questo blog per colleghi e appassionati, dove mi "appunto" quello che imparo. Sono un NERD anni '80, motociclista e orgoglioso papà di due bambini.
Link:
Giovanni Sacheli
SEO Audit Avanzato
Searcus Swiss Sagl
SEOdata.app
cluster.army