MCP Server per WordPress: Connettere l’AI al Tuo Sito con il Model Context Protocol

24 agosto 202614 minAutomazione

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:

  1. Scrivere uno script che chiama la REST API di WordPress
  2. Estrarre e formattare i dati in modo che il modello possa capirli
  3. Gestire l’autenticazione (Application Passwords, JWT, OAuth)
  4. 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 slug
  • create_post — crea una bozza (richiede approvazione utente)
  • list_plugins — elenca plugin installati con stato
  • get_site_health — metriche PHP, database, versione WP
  • list_users — elenca utenti con ruolo

Resources (dati leggibili)

  • wp://posts/{id} — contenuto completo di un post
  • wp://pages/{id} — contenuto di una pagina
  • wp://plugins — lista plugin con versioni
  • wp://health — snapshot Site Health

Prompts (template predefiniti)

  • seo-audit — genera un audit SEO per un post specifico
  • content-brief — crea un brief per un nuovo articolo basato su keyword
  • performance-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:

  1. Mai hardcodare le credenziali: usa sempre variabili d’ambiente, come nell’esempio sopra
  2. Application Passwords con permessi minimi: crea un utente dedicato con ruolo Editor o Author, mai Administrator se non strettamente necessario
  3. HTTPS obbligatorio: la REST API deve girare su HTTPS, mai su HTTP in produzione
  4. Rate limiting: configura rate limiting sul server WordPress (Nginx o plugin) per prevenire abusi
  5. Logging: registra ogni chiamata REST API per audit. Per server STDIO, usa sys.stderr mai print() (corrompe i messaggi JSON-RPC)
  6. Approvazione utente per write operations: il tool create_post crea 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.

Gestisci i siti WordPress dei tuoi clienti?

AgencyPilot ti dà report AI, uptime monitoring, backup e portale clienti in un’unica dashboard. Gratis per 3 siti.

Prova gratis
Leggi anche
Tutti gli articoli
Tutti gli articoli