Il Model Context Protocol (MCP) è lo standard open source che permette ai modelli AI come Claude e ChatGPT di connettersi a sistemi esterni: database, API, file system e, soprattutto, siti WordPress. In questa guida pratica vediamo come costruire un MCP server per WordPress, esponendo post, plugin, utenti e metriche a qualsiasi client AI compatibile.
Nella nostra esperienza in AgencyPilot, gestire 50+ siti WordPress significa automatizzare tutto ciò che è ripetitivo. Il Model Context Protocol apre scenari nuovi: un singolo server MCP può esporre il contenuto di WordPress a Claude, Cursor, VS Code e qualsiasi altro client MCP, senza scrivere integrazioni separate per ogni tool.
TL;DR
- MCP è un protocollo standardizzato (JSON-RPC 2.0) per connettere AI a sistemi esterni
- Un MCP server per WordPress espone post, pagine, plugin, utenti e metriche via tools, resources e prompts
- Si costruisce in Python con
mcp[cli]SDK o in TypeScript con@modelcontextprotocol/sdk - I client MCP (Claude Desktop, VS Code, Cursor) si connettono automaticamente
- Casi d’uso reali per agenzie: audit automatici, generazione report, gestione contenuti multi-sito
Cos’è il Model Context Protocol (MCP)
Il Model Context Protocol è uno standard open source introdotto da Anthropic nel novembre 2024 per risolvere un problema concreto: ogni integrazione tra AI e sistemi esterni richiede codice custom. MCP sostituisce queste integrazioni frammentate con un singolo protocollo universale, paragonabile a una “porta USB-C per applicazioni AI”.
L’architettura MCP segue un modello client-server:
- MCP Host: l’applicazione AI che coordina le connessioni (es. Claude Desktop, VS Code, Cursor)
- MCP Client: il componente che mantiene la connessione con un singolo MCP server
- MCP Server: il programma che espone dati e funzionalità (es. il nostro server WordPress)
MCP supporta due trasporti:
- STDIO: comunicazione locale tra processi sulla stessa macchina, zero overhead di rete
- Streamable HTTP: comunicazione remota con autenticazione OAuth, ideale per server in produzione
Il protocollo si divide in due layer: il data layer (JSON-RPC 2.0 per messaggi, lifecycle e primitive) e il transport layer (canali di comunicazione e autenticazione). Questa separazione permette di usare lo stesso formato di messaggi su qualsiasi trasporto.
Perché WordPress ha bisogno di un MCP Server
WordPress espone già la REST API, quindi perché aggiungere un altro layer? La risposta sta nella standardizzazione del contesto AI.
Senza MCP, ogni volta che vuoi far leggere contenuti WordPress a un modello AI, devi:
- Scrivere uno script che chiama la REST API di WordPress
- Estrarre e formattare i dati in modo che il modello possa capirli
- Gestire l’autenticazione (Application Passwords, JWT, OAuth)
- Ripetere il processo per ogni client AI diverso
Con un MCP server WordPress, scrivi l’integrazione una volta sola. Qualsiasi client MCP — Claude Desktop, ChatGPT, VS Code Copilot, Cursor — può connettersi e accedere ai tuoi dati WordPress. Questo è particolarmente rilevante per le agenzie che usano l’automazione AI per gestire multi-sito WordPress.
| Caratteristica | REST API WordPress | MCP Server WordPress |
|---|---|---|
| Protocollo | HTTP/JSON | JSON-RPC 2.0 su STDIO o HTTP |
| Client supportati | Qualsiasi client HTTP | Claude, ChatGPT, VS Code, Cursor, qualsiasi client MCP |
| Scoperta automatica | No (endpoint hardcoded) | Sì (capability negotiation) |
| Schema dei dati | Definito da WordPress Core | Definito dal server MCP (personalizzabile) |
| Contesto AI | Richiede preprocessing | Nativo: tools, resources, prompts |
Architettura dell’MCP Server WordPress
Il nostro MCP server espone tre tipi di primitive, come previsto dal protocollo:
Tools (funzioni che l’AI può chiamare)
list_posts— elenca post con filtri (status, categoria, ricerca)get_post— recupera un singolo post per ID o slugcreate_post— crea una bozza (richiede approvazione utente)list_plugins— elenca plugin installati con statoget_site_health— metriche PHP, database, versione WPlist_users— elenca utenti con ruolo
Resources (dati leggibili)
wp://posts/{id}— contenuto completo di un postwp://pages/{id}— contenuto di una paginawp://plugins— lista plugin con versioniwp://health— snapshot Site Health
Prompts (template predefiniti)
seo-audit— genera un audit SEO per un post specificocontent-brief— crea un brief per un nuovo articolo basato su keywordperformance-report— produce un report performance per il sito
Come Costruire un MCP Server per WordPress in Python
Usiamo l’SDK Python ufficiale di MCP (mcp[cli] 1.2.0+). WordPress comunica via REST API, quindi il nostro server MCP funge da ponte tra il client AI e WordPress.
Prerequisiti
- Python 3.10+
- Un sito WordPress con REST API abilitata
- Application Passwords generate da WordPress (Users → Profilo → Application Passwords)
- SDK MCP:
uv add "mcp[cli]" httpx
Setup del progetto
# Crea il progetto
uv init wp-mcp-server
cd wp-mcp-server
# Ambiente virtuale e dipendenze
uv venv
source .venv/bin/activate
uv add "mcp[cli]" httpx
# File principale
touch wp_mcp.py
Codice completo del server
from typing import Any
import httpx
import os
from mcp.server.fastmcp import FastMCP
# Inizializza il server MCP
mcp = FastMCP("wordpress")
# Configurazione WordPress da environment
WP_API_URL = os.environ.get("WP_API_URL", "https://wp-dev.agencypilot.it/wp-json/wp/v2")
WP_USER = os.environ.get("WP_USER", "")
WP_APP_PASSWORD = os.environ.get("WP_APP_PASSWORD", "")
def wp_headers() -> dict:
"""Header di autenticazione Basic Auth per WordPress REST API."""
import base64
credentials = f"{WP_USER}:{WP_APP_PASSWORD}"
token = base64.b64encode(credentials.encode()).decode()
return {
"Authorization": f"Basic {token}",
"Content-Type": "application/json"
}
async def wp_request(method: str, endpoint: str, **kwargs) -> Any:
"""Esegue una richiesta alla REST API di WordPress con error handling."""
url = f"{WP_API_URL}/{endpoint.lstrip('/')}"
async with httpx.AsyncClient() as client:
try:
response = await client.request(
method, url,
headers=wp_headers(),
timeout=30.0,
**kwargs
)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
return {"error": f"HTTP {e.response.status_code}: {e.response.text}"}
except httpx.RequestError as e:
return {"error": f"Request error: {str(e)}"}
# === TOOLS ===
@mcp.tool()
async def list_posts(status: str = "publish", per_page: int = 10, search: str = "") -> str:
"""Elenca post WordPress con filtri opzionali.
Args:
status: Stato del post (publish, draft, future, pending)
per_page: Numero di risultati per pagina (max 100)
search: Termine di ricerca opzionale
"""
params = {"status": status, "per_page": min(per_page, 100)}
if search:
params["search"] = search
posts = await wp_request("GET", "posts", params=params)
if isinstance(posts, dict) and "error" in posts:
return f"Errore: {posts['error']}"
result = []
for p in posts:
result.append(
f"ID: {p['id']} | Titolo: {p['title']['rendered']} | "
f"Stato: {p['status']} | Data: {p['date']} | "
f"Slug: {p['slug']}"
)
return "\n".join(result) if result else "Nessun post trovato."
@mcp.tool()
async def get_post(post_id: int) -> str:
"""Recupera un singolo post WordPress per ID con contenuto completo.
Args:
post_id: ID del post da recuperare
"""
post = await wp_request("GET", f"posts/{post_id}")
if isinstance(post, dict) and "error" in post:
return f"Errore: {post['error']}"
return (
f"Titolo: {post['title']['rendered']}\n"
f"Slug: {post['slug']}\n"
f"Stato: {post['status']}\n"
f"Data: {post['date']}\n"
f"Autore: {post.get('author', 'N/A')}\n"
f"Categorie: {post.get('categories', [])}\n"
f"---\n"
f"{post['content']['rendered']}"
)
@mcp.tool()
async def create_post(title: str, content: str, status: str = "draft") -> str:
"""Crea un nuovo post WordPress come bozza (draft).
Args:
title: Titolo del post
content: Contenuto HTML del post
status: Stato del post (default: draft)
"""
payload = {
"title": title,
"content": content,
"status": status
}
result = await wp_request("POST", "posts", json=payload)
if isinstance(result, dict) and "error" in result:
return f"Errore: {result['error']}"
return (
f"Post creato con successo!\n"
f"ID: {result['id']}\n"
f"URL bozza: {result.get('link', 'N/A')}\n"
f"Stato: {result['status']}"
)
@mcp.tool()
async def list_plugins() -> str:
"""Elenca i plugin installati con stato e versione."""
# Usa l'endpoint /wp/v2/plugins (richiede permessi admin)
plugins = await wp_request("GET", "plugins")
if isinstance(plugins, dict) and "error" in plugins:
return f"Errore: {plugins['error']}"
result = []
for p in plugins:
result.append(
f"Plugin: {p.get('name', 'N/A')} | "
f"Versione: {p.get('version', 'N/A')} | "
f"Stato: {p.get('status', 'N/A')} | "
f"Slug: {p.get('plugin', 'N/A')}"
)
return "\n".join(result) if result else "Nessun plugin trovato."
@mcp.tool()
async def get_site_health() -> str:
"""Recupera le metriche di salute del sito WordPress."""
# Endpoint non standard — usa le REST API interne
loops = await wp_request("GET", "settings")
if isinstance(loops, dict) and "error" in loops:
return f"Errore: {loops['error']}"
return (
f"Titolo sito: {loops.get('title', 'N/A')}\n"
f"Descrizione: {loops.get('description', 'N/A')}\n"
f"Timezone: {loops.get('timezone', 'N/A')}\n"
f"Data formato: {loops.get('date_format', 'N/A')}\n"
f"Lingua: {loops.get('language', 'N/A')}\n"
f"Pagina iniziale: {loops.get('show_on_front', 'N/A')}\n"
)
# === RESOURCES ===
@mcp.resource("wp://posts/{post_id}")
async def get_post_resource(post_id: str) -> str:
"""Resource che espone il contenuto di un post WordPress."""
post = await wp_request("GET", f"posts/{post_id}")
if isinstance(post, dict) and "error" in post:
return f"Errore: {post['error']}"
return post.get("content", {}).get("rendered", "Contenuto non disponibile")
@mcp.resource("wp://plugins")
async def get_plugins_resource() -> str:
"""Resource con la lista completa dei plugin installati."""
plugins = await wp_request("GET", "plugins")
if isinstance(plugins, dict) and "error" in plugins:
return f"Errore: {plugins['error']}"
import json
return json.dumps(plugins, indent=2, ensure_ascii=False)
# === PROMPTS ===
@mcp.prompt()
def seo_audit(post_id: str) -> str:
"""Genera un prompt per audit SEO di un post WordPress."""
return (
f"Recupera il post con ID {post_id} usando il tool get_post. "
f"Analizza il contenuto e produci un audit SEO che include:\n"
f"1. Analisi keyword density (keyword primaria e secondarie)\n"
f"2. Struttura heading (H1, H2, H3)\n"
f"3. Lunghezza meta description (campo excerpt)\n"
f"4. Presenza di link interni ed esterni\n"
f"5. Ottimizzazione GEO (citabilità per motori AI)\n"
f"6. Raccomandazioni specifiche e priorità di intervento"
)
@mcp.prompt()
def content_brief(keyword: str) -> str:
"""Genera un brief per un nuovo articolo basato su una keyword."""
return (
f"Crea un brief per un nuovo articolo WordPress ottimizzato per la keyword: '{keyword}'.\n"
f"Il brief deve includere:\n"
f"1. Titolo SEO (max 60 caratteri con la keyword)\n"
f"2. Meta description (150-160 caratteri)\n"
f"3. Struttura H2 (4-6 sezioni)\n"
f"4. Keyword secondarie e LSI\n"
f"5. Link interni suggeriti ad altri articoli\n"
f"6. Domande per la sezione FAQ\n"
f"Usa il tool list_posts per trovare articoli correlati già pubblicati."
)
if __name__ == "__main__":
mcp.run()
Configurazione del client MCP
Dopo aver creato il server, configura il client MCP. Per Claude Desktop, modifica il file di configurazione:
// ~/Library/Application Support/Claude/claude_desktop_config.json
// (su Linux: ~/.config/claude/claude_desktop_config.json)
{
"mcpServers": {
"wordpress": {
"command": "python",
"args": ["/path/to/wp_mcp.py"],
"env": {
"WP_API_URL": "https://tuosito.it/wp-json/wp/v2",
"WP_USER": "tuo_username",
"WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
}
}
}
}
Per VS Code Copilot, aggiungi la configurazione in .vscode/mcp.json:
{
"servers": {
"wordpress": {
"command": "python",
"args": ["/path/to/wp_mcp.py"],
"env": {
"WP_API_URL": "https://tuosito.it/wp-json/wp/v2",
"WP_USER": "tuo_username",
"WP_APP_PASSWORD": "xxxx xxxx xxxx xxxx xxxx xxxx"
}
}
}
}
Casi d’Uso Reali per Agenzie Web
1. Audit SEO Automatici Multi-Sito
Con un MCP server configurato per ogni sito cliente, un operatore può chiedere a Claude: “Fai un audit SEO degli ultimi 10 post su ogni sito e dammi una tabella riassuntiva”. Il modello AI itera sui siti, recupera i post tramite il tool list_posts, analizza ogni articolo e produce un report strutturato — senza che l’operatore scriva una riga di codice.
Questo è particolarmente potente combinato con le tecniche di ottimizzazione Core Web Vitals: l’AI può identificare post che rallentano il sito e suggerire fix specifici.
2. Generazione di Brief per Nuovi Articoli
Usando il prompt content_brief, l’AI genera un brief completo partendo da una keyword. Il brief include titolo SEO, meta description, struttura H2, keyword secondarie e link interni — tutto basato sugli articoli già pubblicati sul sito, che l’AI recupera automaticamente tramite list_posts.
3. Gestione Centralizzata dei Plugin
Per le agenzie che gestiscono 50+ siti WordPress, il tool list_plugins permette di verificare lo stato dei plugin su ogni sito con una singola conversazione. “Quali siti hanno plugin non aggiornati?” diventa una domanda che Claude può rispondere direttamente.
4. Monitoraggio Site Health
Il tool get_site_health espone le metriche critical del sito. Combinato con un sistema di monitoraggio uptime con AI, permette di identificare problemi prima che diventino incidenti.
Sicurezza: Best Practice per l’MCP Server WordPress
Esporre WordPress a un client AI richiede attenzione alla sicurezza. Ecco le regole che seguiamo in AgencyPilot:
- Mai hardcodare le credenziali: usa sempre variabili d’ambiente, come nell’esempio sopra
- Application Passwords con permessi minimi: crea un utente dedicato con ruolo Editor o Author, mai Administrator se non strettamente necessario
- HTTPS obbligatorio: la REST API deve girare su HTTPS, mai su HTTP in produzione
- Rate limiting: configura rate limiting sul server WordPress (Nginx o plugin) per prevenire abusi
- Logging: registra ogni chiamata REST API per audit. Per server STDIO, usa
sys.stderrmaiprint()(corrompe i messaggi JSON-RPC) - Approvazione utente per write operations: il tool
create_postcrea solo bozze (status=draft), mai post pubblicati direttamente
Per approfondire la sicurezza di WordPress, consulta la nostra guida completa alla sicurezza WordPress per agenzie.
MCP Server Remoto vs Locale: Quale Scegliere
L’SDK MCP supporta due modalità di trasporto, ognuna con pro e contro per le agenzie:
| Aspetto | STDIO (Locale) | Streamable HTTP (Remoto) |
|---|---|---|
| Setup | Più semplice, un file Python | Richiede server HTTP con SSL |
| Autenticazione | Implicita (process locale) | OAuth o Bearer token |
| Scalabilità | Un client per server | Più client per server |
| Casi d’uso | Sviluppo, testing, uso personale | Produzione, team, multi-client |
| Latenza | Minima (zero rete) | Dipende dalla rete |
Per sviluppo e testing, STDIO è perfetto. Per produzione in agenzia con più operatori che usano AI diversi, Streamable HTTP con OAuth è la scelta corretta.
Integrazione con CI/CD e Automazione
L’MCP server WordPress si integra naturalmente in pipeline di automazione esistenti. Alcuni esempi pratici:
Pipeline di pubblicazione automatica
Un workflow n8n o Make può invocare l’API REST di WordPress per creare bozze, poi un operatore usa Claude Desktop con MCP per rivedere e approvare. Il flusso è: n8n → REST API → draft → Claude + MCP → revisione → publish.
Audit periodici automatici
Configura un cron job che lancia un client MCP con un prompt predefinito: “Controlla gli ultimi 20 post, identifica quelli senza meta description o con keyword density fuori range, genera un report”. Il risultato viene inviato su Slack o Discord.
Generazione report clienti
Usa il prompt performance-report per generare report mensili automatici per ogni sito cliente, combinando dati WordPress con metriche di backup e disaster recovery.
FAQ
MCP sostituisce la REST API di WordPress?
No. MCP è un layer aggiuntivo che usa la REST API di WordPress come backend. Il server MCP fa da ponte tra il protocollo MCP (JSON-RPC 2.0) e la REST API di WordPress (HTTP/JSON). La REST API rimane il layer di accesso ai dati.
Serve un plugin WordPress per usare MCP?
No, non serve. L’MCP server è un programma separato che chiama la REST API di WordPress. WordPress non sa di stare parlando con un server MCP — vede solo richieste REST API standard con Basic Auth.
Quali client AI supportano MCP?
Claude Desktop, Claude Code, ChatGPT, VS Code Copilot, Cursor, Zed, Replit, Codeium e Sourcegraph supportano MCP. La lista cresce costantemente. Il protocollo è open source, quindi qualsiasi client può implementarlo.
MCP è sicuro per siti in produzione?
Sì, se segui le best practice: HTTPS, Application Passwords con permessi minimi, rate limiting, e logging. Per server MCP remoti in produzione, usa OAuth invece di Basic Auth. Mai esporre credenziali nel codice.
Posso usare MCP con WordPress Multisite?
Sì. Configura WP_API_URL con l’URL del subsito specifico (es. https://rete.it/sito1/wp-json/wp/v2). Per gestire multipli subsiti, crea un MCP server per ognuno o configura un server unico con un parametro site_id che dinamizza l’URL dell’API.
Quanto costa mantenere un MCP server?
Il costo è praticamente zero per server STDIO locali. Per server HTTP remoti, il costo dipende dall’hosting (un piccolo VPS è sufficiente). L’SDK MCP è open source e gratuito. Il costo reale è lo sviluppo iniziale (poche ore) e la manutenzione.