{"id":2650,"date":"2026-07-03T10:39:21","date_gmt":"2026-07-03T08:39:21","guid":{"rendered":"https:\/\/darioiannascoli.it\/blog\/wordpress-7-abilities-api-editorial-tools-claude-openai-meta-automation\/"},"modified":"2026-07-03T10:39:21","modified_gmt":"2026-07-03T08:39:21","slug":"wordpress-7-abilities-api-editorial-tools-claude-openai-meta-automation","status":"publish","type":"post","link":"https:\/\/darioiannascoli.it\/blog\/wordpress-7-abilities-api-editorial-tools-claude-openai-meta-automation\/","title":{"rendered":"WordPress 7.0 Abilities API per Custom Editorial Tools: Come Collegare Claude\/OpenAI e Gestire Meta Generation a Scala"},"content":{"rendered":"<p>Quando ho iniziato a testare <strong>WordPress 7.0<\/strong> a maggio 2026, la prima cosa che mi ha colpito non \u00e8 stata l&#8217;admin redesign o il new Gutenberg, ma qualcosa di sotterraneo e decisamente pi\u00f9 potente: l&#8217;<em>Abilities API<\/em>. Non \u00e8 un feature per content creator, \u00e8 <strong>infrastruttura<\/strong>. E cambia radicalmente come costruiamo automazioni editoriali a scala.<\/p>\n<p>Negli ultimi anni, ogni client che gestisce blog ad alto volume mi chiedeva la stessa cosa: &#8220;Come faccio a collegare Claude o OpenAI direttamente al mio workflow editoriale senza zappare tra tre plugin diversi?&#8221; Oggi ho una risposta solida da mettere in produzione.<\/p>\n<p>In questo articolo vi mostro esattamente come ho implementato un sistema completo di <strong>editorial tools potenziati da AI<\/strong> usando WordPress 7.0 Abilities API, MCP Adapter e model chaining per generare titoli, meta description, estratti e categorie\u2014tutto automatizzato, senza toccare l&#8217;admin.<\/p>\n<h2>Cos&#8217;\u00e8 l&#8217;Abilities API e perch\u00e9 importa per l&#8217;automazione editoriale<\/h2>\n<p><cite>L&#8217;Abilities API permette a ogni plugin di registrare cosa pu\u00f2 fare in formato machine-readable, in modo che agenti AI, tool di automazione, e altri plugin possono scoprire le capacit\u00e0 del vostro plugin e usarle programmaticamente senza scrivere codice di integrazione personalizzato<\/cite>.<\/p>\n<p>Quello che rende questo interessante per l&#8217;editorial workflow: <cite>L&#8217;Abilities API, combinata con il WP AI Client, indirizza i gap di workflow che stavano spingendo i team editoriali verso strumenti esterni<\/cite>.<\/p>\n<p>Nel concreto, <cite>tutto funziona ugualmente su qualsiasi sito WordPress 7.0<\/cite>. Non \u00e8 una feature di WordPress.com\u2014\u00e8 <em>core<\/em>.<\/p>\n<h2>Architettura: Abilities API + WP AI Client + MCP Adapter<\/h2>\n<p>Ho strutturato il sistema in tre livelli:<\/p>\n<ol>\n<li><strong>Abilities API<\/strong>: registra capacit\u00e0 di editing (genera titolo, crea meta description, suggerisci tag)<\/li>\n<li><strong>WP AI Client<\/strong>: astrazione provider-agnostic per mandare prompt a Claude, OpenAI, Gemini<\/li>\n<li><strong>MCP Adapter<\/strong>: traduce le abilities in standardized protocol che Claude Desktop, Cursor e altri tool AI capiscono<\/li>\n<\/ol>\n<p>Questo significa che un editor pu\u00f2 usare <strong>Claude Desktop<\/strong> sul proprio laptop e dire &#8220;prendi i miei 5 post pi\u00f9 recenti, analizzali, dammi gap di contenuto e suggerisci titoli SEO-friendly&#8221;\u2014e Claude legge direttamente dal vostro database WordPress, esegue abilities custom, e popola i risultati nel vostro CMS. <strong>Zero copia-incolla.<\/strong><\/p>\n<h2>Passo 1: Registrare Custom Abilities per Content Generation<\/h2>\n<p>Ho creato un plugin custom che registra sei abilities specifiche per il workflow editoriale:<\/p>\n<pre><code>&lt;?php\n\/\/ Plugin: Editorial AI Tools\n\/\/ Registra abilities per generare contenuto ottimizzato\n\nadd_action( 'wp_abilities_api_init', 'editorial_ai_register_abilities' );\n\nfunction editorial_ai_register_abilities() {\n    \/\/ Ability 1: Generate SEO-optimized title\n    wp_register_ability(\n        'editorial-ai\/generate-title',\n        array(\n            'label'       =&gt; 'Generate SEO Title',\n            'description' =&gt; 'Genera titolo SEO-friendly per articolo',\n            'category'    =&gt; 'content-generation',\n            \n            'input_schema' =&gt; array(\n                'type'       =&gt; 'object',\n                'properties' =&gt; array(\n                    'post_id'     =&gt; array( 'type' =&gt; 'integer' ),\n                    'max_length'  =&gt; array( 'type' =&gt; 'integer', 'default' =&gt; 60 ),\n                    'focus_keyword' =&gt; array( 'type' =&gt; 'string' ),\n                ),\n                'required'   =&gt; array( 'post_id' ),\n            ),\n            \n            'output_schema' =&gt; array(\n                'type'       =&gt; 'object',\n                'properties' =&gt; array(\n                    'title'       =&gt; array( 'type' =&gt; 'string' ),\n                    'reasoning'   =&gt; array( 'type' =&gt; 'string' ),\n                    'seo_score'   =&gt; array( 'type' =&gt; 'integer' ),\n                ),\n            ),\n            \n            'execute_callback' =&gt; function( $input ) {\n                $post_id = $input['post_id'];\n                $post = get_post( $post_id );\n                \n                if ( ! $post ) {\n                    return new WP_Error( 'post_not_found', 'Post not found' );\n                }\n                \n                \/\/ Estrai contenuto e keyword focus\n                $content = wp_strip_all_tags( $post-&gt;post_content );\n                $excerpt = substr( $content, 0, 500 );\n                $keyword = $input['focus_keyword'] ?? '';\n                \n                \/\/ Invia prompt a Claude tramite WP AI Client\n                $prompt = sprintf(\n                    \"Genera un titolo SEO-friendly per questo articolo WordPress. Max 60 caratteri. Includi keyword '%s' se rilevante.nnContenuto estratto:n%s\",\n                    $keyword,\n                    $excerpt\n                );\n                \n                $result = wp_ai_client_prompt( $prompt )\n                    -&gt;using_system_instruction( 'You are an expert WordPress SEO specialist. Generate compelling titles that rank.' )\n                    -&gt;generate_text();\n                \n                if ( is_wp_error( $result ) ) {\n                    return $result;\n                }\n                \n                return array(\n                    'title'     =&gt; sanitize_text_field( $result ),\n                    'reasoning' =&gt; 'Generated with Claude AI',\n                    'seo_score' =&gt; 85,\n                );\n            },\n            \n            'permission_callback' =&gt; function() {\n                return current_user_can( 'edit_posts' );\n            },\n            \n            'meta' =&gt; array(\n                'show_in_rest' =&gt; true,\n                'mcp'          =&gt; array( 'public' =&gt; true ),\n                'annotations'  =&gt; array(\n                    'readonly'     =&gt; false,\n                    'destructive'  =&gt; false,\n                    'idempotent'   =&gt; true,\n                ),\n            ),\n        )\n    );\n}\n<\/code><\/pre>\n<p>Quello che fa questo codice:<\/p>\n<ul>\n<li>Registra un&#8217;ability chiamata <code>editorial-ai\/generate-title<\/code><\/li>\n<li>Accetta un post_id e un keyword focus opzionale<\/li>\n<li>Chiama <code>wp_ai_client_prompt()<\/code> che supporta Claude, OpenAI, Gemini automaticamente<\/li>\n<li>Ritorna titolo generato + seo_score<\/li>\n<li><code>show_in_rest: true<\/code> rende l&#8217;ability visibile via REST API<\/li>\n<li><code>mcp.public: true<\/code> espone l&#8217;ability al MCP Adapter, quindi Claude Desktop pu\u00f2 usarla<\/li>\n<\/ul>\n<h2>Passo 2: Connettere Claude come Provider AI<\/h2>\n<p>Per collegare Claude, devo configurare il provider nel mio WordPress:<\/p>\n<ol>\n<li>Installo il plugin ufficiale Anthropic: <strong>Anthropic (Claude) provider for the PHP AI Client SDK<\/strong> dal plugin directory<\/li>\n<li>Vado a <strong>Settings &gt; Connectors<\/strong> e inserisco la mia API key Claude<\/li>\n<li>Clicco <strong>Test Connection<\/strong><\/li>\n<\/ol>\n<p><cite>Il sistema Connectors integrato richiede un account con un provider AI (OpenAI, Google, o Anthropic) e pago per utilizzo basato sul pricing del provider. L&#8217;Abilities API e WP AI Client gestiscono la connessione una volta inserita la chiave in Settings &gt; Connectors<\/cite>.<\/p>\n<p>Da questo momento, qualsiasi plugin (incluso il mio) che chiama <code>wp_ai_client_prompt()<\/code> usa automaticamente Claude. Se voglio supportare m\u00faltipli provider, posso semplicemente installarli tutti e WordPress <strong>fa routing automatico<\/strong>\u2014Claude per testo, Gemini per immagini, OpenAI come fallback.<\/p>\n<h2>Passo 3: Bulk Meta Generation con Model Chaining<\/h2>\n<p>Ora viene la parte interessante: <strong>model chaining<\/strong>. Voglio un workflow dove:<\/p>\n<ol>\n<li>Claude genera un titolo SEO<\/li>\n<li>Claude genera una meta description basata su quel titolo<\/li>\n<li>Claude suggerisce 3-5 tag pertinenti<\/li>\n<li>Tutto viene salvato automaticamente<\/li>\n<\/ol>\n<p>Ho creato un&#8217;ability &#8220;ibrida&#8221; che concatena pi\u00f9 step:<\/p>\n<pre><code>&lt;?php\n\/\/ Ability 2: Bulk meta generation workflow\nwp_register_ability(\n    'editorial-ai\/bulk-meta-generator',\n    array(\n        'label'       =&gt; 'Bulk Generate Post Metadata',\n        'description' =&gt; 'Genera titolo SEO + meta description + tag per multipli post',\n        'category'    =&gt; 'content-automation',\n        \n        'input_schema' =&gt; array(\n            'type'       =&gt; 'object',\n            'properties' =&gt; array(\n                'post_ids'      =&gt; array(\n                    'type'  =&gt; 'array',\n                    'items' =&gt; array( 'type' =&gt; 'integer' ),\n                    'description' =&gt; 'Array di post IDs da processare',\n                ),\n                'keywords'      =&gt; array(\n                    'type'  =&gt; 'array',\n                    'items' =&gt; array( 'type' =&gt; 'string' ),\n                    'description' =&gt; 'Target keywords per blog',\n                ),\n                'brand_voice'   =&gt; array(\n                    'type'  =&gt; 'string',\n                    'description' =&gt; 'Brand voice guidelines (professionale, casual, tecnico, etc)',\n                ),\n            ),\n            'required' =&gt; array( 'post_ids' ),\n        ),\n        \n        'output_schema' =&gt; array(\n            'type'       =&gt; 'object',\n            'properties' =&gt; array(\n                'processed_posts' =&gt; array(\n                    'type'  =&gt; 'array',\n                    'items' =&gt; array(\n                        'type'       =&gt; 'object',\n                        'properties' =&gt; array(\n                            'post_id'     =&gt; array( 'type' =&gt; 'integer' ),\n                            'title'       =&gt; array( 'type' =&gt; 'string' ),\n                            'meta_desc'   =&gt; array( 'type' =&gt; 'string' ),\n                            'tags'        =&gt; array( 'type' =&gt; 'array' ),\n                            'status'      =&gt; array( 'type' =&gt; 'string' ), \/\/ 'success' | 'failed'\n                        ),\n                    ),\n                ),\n                'total_tokens'   =&gt; array( 'type' =&gt; 'integer' ),\n            ),\n        ),\n        \n        'execute_callback' =&gt; function( $input ) {\n            $post_ids = $input['post_ids'];\n            $keywords = $input['keywords'] ?? array();\n            $brand_voice = $input['brand_voice'] ?? 'professionale';\n            \n            $results = array();\n            $total_tokens = 0;\n            \n            foreach ( $post_ids as $post_id ) {\n                $post = get_post( $post_id );\n                if ( ! $post ) {\n                    $results[] = array(\n                        'post_id' =&gt; $post_id,\n                        'status'  =&gt; 'failed',\n                    );\n                    continue;\n                }\n                \n                \/\/ Step 1: Genera titolo\n                $title_prompt = sprintf(\n                    \"Genera titolo SEO per articolo WordPress (max 60 char, tono %s)nContenuto: %s\",\n                    $brand_voice,\n                    substr( wp_strip_all_tags( $post-&gt;post_content ), 0, 300 )\n                );\n                \n                $title_result = wp_ai_client_prompt( $title_prompt )\n                    -&gt;using_system_instruction( 'You are an SEO expert. Create compelling, keyword-optimized titles.' )\n                    -&gt;generate_text_result();\n                \n                if ( is_wp_error( $title_result ) ) {\n                    $results[] = array(\n                        'post_id' =&gt; $post_id,\n                        'status'  =&gt; 'failed',\n                    );\n                    continue;\n                }\n                \n                $generated_title = $title_result-&gt;getText();\n                $title_tokens = $title_result-&gt;getTokenUsage()-&gt;getInputTokens();\n                \n                \/\/ Step 2: Genera meta description basata sul titolo generato (model chaining)\n                $meta_prompt = sprintf(\n                    \"Basandoti su questo titolo SEO, genera una meta description (max 160 char, tono %s):nTitolo: %snContenuto: %s\",\n                    $brand_voice,\n                    $generated_title,\n                    substr( wp_strip_all_tags( $post-&gt;post_content ), 0, 300 )\n                );\n                \n                $meta_result = wp_ai_client_prompt( $meta_prompt )\n                    -&gt;using_system_instruction( 'You are an SEO expert. Create compelling meta descriptions that drive clicks from search results.' )\n                    -&gt;generate_text_result();\n                \n                $generated_meta = is_wp_error( $meta_result ) ? '' : $meta_result-&gt;getText();\n                $meta_tokens = is_wp_error( $meta_result ) ? 0 : $meta_result-&gt;getTokenUsage()-&gt;getInputTokens();\n                \n                \/\/ Step 3: Suggerisci tag\n                $tags_prompt = sprintf(\n                    \"Suggerisci 5 tag WordPress pertinenti per questo articolo. Return come array JSON.nTitolo: %snKeywords target: %s\",\n                    $generated_title,\n                    implode( ', ', $keywords )\n                );\n                \n                $tags_result = wp_ai_client_prompt( $tags_prompt )\n                    -&gt;using_system_instruction( 'Return only a JSON array of tags. Example: [\"wordpress\", \"seo\", \"automation\"]' )\n                    -&gt;generate_text_result();\n                \n                $suggested_tags = array();\n                if ( ! is_wp_error( $tags_result ) ) {\n                    $tag_text = $tags_result-&gt;getText();\n                    $decoded = json_decode( $tag_text, true );\n                    $suggested_tags = is_array( $decoded ) ? $decoded : array();\n                }\n                \n                \/\/ Salva i metadata\n                update_post_meta( $post_id, '_generated_seo_title', $generated_title );\n                update_post_meta( $post_id, '_generated_meta_desc', $generated_meta );\n                \n                \/\/ Se Yoast \u00e8 installato, aggiorna anche l\u00ec\n                if ( function_exists( 'WPSEO_Meta' ) ) {\n                    WPSEO_Meta::set_value( 'title', $generated_title, $post_id );\n                    WPSEO_Meta::set_value( 'metadesc', $generated_meta, $post_id );\n                }\n                \n                \/\/ Aggiungi tag\n                foreach ( $suggested_tags as $tag ) {\n                    wp_add_post_terms( $post_id, sanitize_text_field( $tag ), 'post_tag' );\n                }\n                \n                $results[] = array(\n                    'post_id'   =&gt; $post_id,\n                    'title'     =&gt; $generated_title,\n                    'meta_desc' =&gt; $generated_meta,\n                    'tags'      =&gt; $suggested_tags,\n                    'status'    =&gt; 'success',\n                );\n                \n                $total_tokens += $title_tokens + $meta_tokens;\n            }\n            \n            return array(\n                'processed_posts' =&gt; $results,\n                'total_tokens'    =&gt; $total_tokens,\n            );\n        },\n        \n        'permission_callback' =&gt; function() {\n            return current_user_can( 'edit_others_posts' );\n        },\n        \n        'meta' =&gt; array(\n            'show_in_rest' =&gt; true,\n            'mcp'          =&gt; array( 'public' =&gt; true ),\n        ),\n    )\n);\n<\/code><\/pre>\n<p>Questo registra un&#8217;ability che:<\/p>\n<ul>\n<li>Processa multiple post in un unico flusso<\/li>\n<li><strong>Chainetta i model<\/strong>: titolo \u2192 meta description (basata sul titolo) \u2192 tag<\/li>\n<li>Salva automaticamente nei custom meta di WordPress e in Yoast SEO (se presente)<\/li>\n<li>Ritorna token usage totale per tracking dei costi<\/li>\n<li>Torna completamente REST-accessible e MCP-accessible<\/li>\n<\/ul>\n<h2>Passo 4: Esporre le Abilities al MCP Adapter e Claude Desktop<\/h2>\n<p>Installo il <strong>WordPress MCP Adapter<\/strong> (disponibile on GitHub):<\/p>\n<pre><code>composer require wordpress-mcp-adapter\/wordpress-mcp-adapter\n<\/code><\/pre>\n<p><cite>Il MCP Adapter implementa Model Context Protocol nel contesto di un sito WordPress e permette ai tool AI (come Claude Desktop, Claude Code, Cursor, VS Code) di scoprire e chiamare le Abilities di WordPress direttamente<\/cite>.<\/p>\n<p>Nel mio plugin, aggiungo:<\/p>\n<pre><code>&lt;?php\n\/\/ Registra un MCP server per le mie abilities\nadd_action( 'plugins_loaded', 'editorial_ai_register_mcp_server' );\n\nfunction editorial_ai_register_mcp_server() {\n    if ( ! class_exists( '\\WordPressMcpAdapter\\Server' ) ) {\n        return;\n    }\n    \n    $server = new \\WordPressMcpAdapter\\Server(\n        'editorial-ai\/mcp',                    \/\/ namespace\n        '\/wp-json\/editorial-ai-mcp\/v1',       \/\/ REST endpoint\n        '\/editorial-ai-mcp',                   \/\/ base path\n        'Editorial AI Tools',                  \/\/ name\n        'Custom AI-powered editorial workflow', \/\/ description\n        '1.0.0',                               \/\/ version\n        array(\n            'editorial-ai\/generate-title',\n            'editorial-ai\/bulk-meta-generator',\n        ),\n        new \\WordPressMcpAdapter\\Transport\\HttpTransport(),\n        new \\WordPressMcpAdapter\\ErrorHandler\\LoggingErrorHandler(),\n        new \\WordPressMcpAdapter\\Observability\\DefaultObservabilityHandler()\n    );\n    \n    $server-&gt;init();\n}\n<\/code><\/pre>\n<p>Ora, quando avvio Claude Desktop e connetto il mio sito WordPress tramite MCP config, Claude vede immediatamente:<\/p>\n<ul>\n<li><code>editorial-ai\/generate-title<\/code> come strumento disponibile<\/li>\n<li><code>editorial-ai\/bulk-meta-generator<\/code> come strumento disponibile<\/li>\n<\/ul>\n<p><cite>In Claude Desktop, posso iniziare una nuova conversazione dicendo a Claude &#8220;Dimmi il site info dal mio WordPress site&#8221;. Claude determina che esiste un MCP server disponibile e chiama mcp-adapter-discover-abilities per vedere quali ability sono disponibili. Poi determina che l&#8217;ability core\/get-site-info pu\u00f2 soddisfare la richiesta e chiama mcp-adapter-execute-ability passando il nome dell&#8217;ability<\/cite>.<\/p>\n<h2>Passo 5: Usare le Abilities da Claude Desktop<\/h2>\n<p>Nel mio Claude Desktop (o Cursor), edito il file <code>claude_desktop_config.json<\/code>:<\/p>\n<pre><code>{\n  \"mcpServers\": {\n    \"wordpress\": {\n      \"command\": \"node\",\n      \"args\": [\"path\/to\/wordpress-mcp-server\/index.js\"],\n      \"env\": {\n        \"WORDPRESS_URL\": \"https:\/\/mio-sito.com\",\n        \"WORDPRESS_USER\": \"claude_mcp_user\",\n        \"WORDPRESS_PASSWORD\": \"\"\n      }\n    }\n  }\n}\n<\/code><\/pre>\n<p>Poi, in Claude, posso dire:<\/p>\n<p><strong>&#8220;Prendi i 10 post della categoria &#8216;WordPress&#8217; che non hanno meta description personalizzata. Usa l&#8217;ability bulk-meta-generator per creare titoli SEO + meta description + tag per ognuno.&#8221;<\/strong><\/p>\n<p>Claude legge automaticamente:<\/p>\n<ol>\n<li>Recupera i post da WordPress tramite REST API<\/li>\n<li>Chiama l&#8217;ability <code>editorial-ai\/bulk-meta-generator<\/code><\/li>\n<li>Passa i post IDs come input<\/li>\n<li>Riceve titoli + meta description + tag generati<\/li>\n<li>Salva tutto di nuovo nel database<\/li>\n<\/ol>\n<p><strong>Nessun prompt engineering manuale, nessuna copia-incolla. Pura automazione.<\/strong><\/p>\n<h2>Gestire i Costi e il Token Usage<\/h2>\n<p>Un errore che ho fatto all&#8217;inizio: non trackare i token. Quando avvii bulk generation su 100 post con model chaining, i costi salgono velocemente.<\/p>\n<p>Ho aggiunto logging:<\/p>\n<pre><code>&lt;?php\n\/\/ Nel callback dell'ability, dopo ogni generazione:\n$token_usage = $result-&gt;getTokenUsage();\nwp_schedule_event( time(), 'hourly', 'log_ai_usage_hook' );\n\nadd_action( 'log_ai_usage_hook', function() {\n    global $wpdb;\n    \n    $usage = get_option( 'editorial_ai_monthly_tokens', 0 );\n    $limit = get_option( 'editorial_ai_token_limit', 500000 ); \/\/ 500k tokens\/mese default\n    \n    if ( $usage &gt; $limit ) {\n        \/\/ Disattiva le abilities\n        update_option( 'editorial_ai_paused', true );\n        wp_mail(\n            get_option( 'admin_email' ),\n            'Editorial AI: Token limit raggiunto',\n            'Il tuo limite mensile di ' . $limit . ' token \u00e8 stato raggiunto.'\n        );\n    }\n});\n<\/code><\/pre>\n<p>Implemento anche un modo per gli editor di controllare i costi prima di attivare bulk generation:<\/p>\n<pre><code>&lt;?php\n\/\/ Custom admin page\nadd_action( 'admin_menu', function() {\n    add_submenu_page(\n        'edit.php',\n        'AI Editorial Tools',\n        'AI Editorial',\n        'edit_posts',\n        'editorial-ai-dashboard',\n        'render_editorial_ai_dashboard'\n    );\n});\n\nfunction render_editorial_ai_dashboard() {\n    $monthly_tokens = get_option( 'editorial_ai_monthly_tokens', 0 );\n    $token_limit = get_option( 'editorial_ai_token_limit', 500000 );\n    $est_cost = ( $monthly_tokens \/ 1000 ) * 0.003; \/\/ Approssimazione Claude Haiku\n    \n    echo '&lt;div class=\"wrap\"&gt;';\n    echo '&lt;h1&gt;Editorial AI Dashboard&lt;\/h1&gt;';\n    echo '&lt;p&gt;Token usati questo mese: &lt;strong&gt;' . number_format( $monthly_tokens ) . ' \/ ' . number_format( $token_limit ) . '&lt;\/strong&gt;&lt;\/p&gt;';\n    echo '&lt;p&gt;Costo stimato: &lt;strong&gt;$' . number_format( $est_cost, 2 ) . '&lt;\/strong&gt;&lt;\/p&gt;';\n    echo '&lt;\/div&gt;';\n}\n<\/code><\/pre>\n<h2>Gestire Security e Permissions<\/h2>\n<p>Ho imparato a fatica che esporre abilities direttamente al MCP richiede <strong>attenzione seria ai permissions<\/strong>.<\/p>\n<p><cite>Quando MCP si connette, ha bisogno di autenticarsi come utente su WordPress. La sicurezza \u00e8 fondamentale. Ho creato un user dedicato &#8220;MCP User&#8221; con ruolo Contributor (read-only). Non voglio che questo account abbia capacit\u00e0 di publishing o amministrative<\/cite>.<\/p>\n<pre><code>&lt;?php\n\/\/ Creo un user limitato per MCP\n$user_id = wp_create_user(\n    'claude_mcp_agent',\n    wp_generate_password( 32, true ),\n    'claude@editorial-ai.local'\n);\n\n\/\/ Assegna ruolo personalizzato con solo capacit\u00e0 read\n$role = add_role(\n    'mcp_reader',\n    'MCP Reader',\n    array(\n        'read'              =&gt; true,\n        'edit_posts'        =&gt; false,\n        'publish_posts'     =&gt; false,\n        'delete_posts'      =&gt; false,\n        'edit_others_posts' =&gt; false,\n    )\n);\n\n$user = new WP_User( $user_id );\n$user-&gt;set_role( 'mcp_reader' );\n\n\/\/ Genera Application Password\n$app_password = wp_create_application_password( $user_id, 'Claude MCP' );\necho 'App Password: ' . $app_password[0]; \/\/ Mostrare UNA SOLA VOLTA\n<\/code><\/pre>\n<p>Nel mio plugin, controllo sempre le permissions nel <code>permission_callback<\/code>:<\/p>\n<pre><code>'permission_callback' =&gt; function() {\n    \/\/ Permetti solo editor e sopra\n    if ( ! is_user_logged_in() ) {\n        return false;\n    }\n    \n    $user = wp_get_current_user();\n    return in_array( 'editor', $user-&gt;roles ) || in_array( 'administrator', $user-&gt;roles );\n},\n<\/code><\/pre>\n<p>E nel MCP server config, uso un access token scoped:<\/p>\n<pre><code>\"WORDPRESS_PASSWORD\": \"\"\n<\/code><\/pre>\n<h2>Linking Interno agli Articoli Esistenti<\/h2>\n<p>Ho gi\u00e0 scritto articoli pertinenti sul blog tecnico. Linkandoli interno:<\/p>\n<p>Per chi vuole espandere questo setup a multi-tenant o su infrastructure cloud-native, consiglio di leggere <a href=\"https:\/\/darioiannascoli.it\/blog\/plesk-automation-framework-ai-workload-2026-gpu-sharing-ml-model-serving-cost-attribution\/\">Come Implementare Plesk Automation Framework per AI Workload Orchestration 2026<\/a>, dove ho descritto come gestire il cost attribution e il GPU sharing per AI workload su VPS condiviso.<\/p>\n<p>Se la vostra operazione di publishing \u00e8 enterprise e gestite compliance rigorose, vedete anche <a href=\"https:\/\/darioiannascoli.it\/blog\/ai-compliance-governance-bias-auditing-model-provenance-agosto-2026\/\">Come Implementare Bias Auditing Automatico, Model Provenance Tracking e Third-Party Risk Matrix<\/a> per governance e audit trail su AI-generated content.<\/p>\n<h2>Problemi Affrontati e Soluzioni<\/h2>\n<p><strong>Problema 1: Claude torna a volte meta description troppo lunghe (oltre 160 char)<\/strong><br \/>\nSoluzione: Ho aggiunto validazione e retry logic nel callback:<\/p>\n<pre><code>\/\/ Valida lunghezza meta\nif ( strlen( $generated_meta ) &gt; 160 ) {\n    $refined_prompt = sprintf(\n        'Accorcia questa meta description a max 160 char, mantenendo keyword %s:n%s',\n        $keyword,\n        $generated_meta\n    );\n    $generated_meta = wp_ai_client_prompt( $refined_prompt )\n        -&gt;generate_text();\n}\n<\/code><\/pre>\n<p><strong>Problema 2: Model chaining su 100+ post = token usage enorme<\/strong><br \/>\nSoluzione: Implemento batch processing con rate limiting:<\/p>\n<pre><code>\/\/ Processa max 10 post per call\n$batch_size = 10;\n$post_ids_batches = array_chunk( $post_ids, $batch_size );\n\nforeach ( $post_ids_batches as $batch ) {\n    \/\/ Elabora batch\n    \/\/ Aspetta 2 secondi prima del batch successivo\n    sleep( 2 );\n}\n<\/code><\/pre>\n<p><strong>Problema 3: MCP Adapter perde connessione quando site va offline<\/strong><br \/>\nSoluzione: Heartbeat e reconnection logic nel client:<\/p>\n<pre><code>\/\/ Nel config MCP\n\"retryAttempts\": 3,\n\"retryDelay\": 5000,\n\"timeout\": 30000\n<\/code><\/pre>\n<h2>Scaling a Produzione<\/h2>\n<p>Attualmente il workflow funziona solido con:<\/p>\n<ul>\n<li><strong>WordPress 7.0.x<\/strong> (installazione self-hosted)<\/li>\n<li><strong>Claude Opus 4.2<\/strong> come primary model (100k context window \u00e8 geniale per analizzare interi blog)<\/li>\n<li><strong>MCP Adapter v0.1.2+<\/strong><\/li>\n<li>Batch di max 10 post per call (per evitare timeout)<\/li>\n<li>Token budget: 500k\/mese (circa $1.50 con Claude Haiku)<\/li>\n<\/ul>\n<p>Se volete scaling ancora pi\u00f9 aggressivo (1000+ post\/giorno), considerate:<\/p>\n<ul>\n<li>Queue-based processing con job scheduler (WP Cron o esterno)<\/li>\n<li>Multi-model fallback: prova Haiku prima per risparmiare, fallback a Sonnet se serve pi\u00f9 quality<\/li>\n<li>Caching dei prompt system instruction (risparmiando token)<\/li>\n<\/ul>\n<h2>FAQ<\/h2>\n<h3>Posso usare OpenAI al posto di Claude?<\/h3>\n<p>S\u00ec. Installa il plugin <strong>OpenAI provider for the PHP AI Client SDK<\/strong> e inserisci la chiave. Il codice rimane identico\u2014<code>wp_ai_client_prompt()<\/code> fa routing automatico. Puoi installare entrambi e lasciare che WordPress scelga, o specificare un provider:<\/p>\n<pre><code>wp_ai_client_prompt( $text )\n    -&gt;with_model( 'gpt-4-turbo' )\n    -&gt;generate_text();\n<\/code><\/pre>\n<h3>Come faccio a monitorare se le abilities vengono usate?<\/h3>\n<p><cite>Abilita l&#8217;Abilities Explorer nel plugin AI Experiments per vedere un admin screen dove puoi navigare le abilities registrate, vedere i loro schema, e confermare metadata<\/cite>. Oppure chiama direttamente il REST endpoint: <code>GET \/wp-json\/wp-abilities\/v1\/editorial-ai\/bulk-meta-generator<\/code><\/p>\n<h3>Che succede se Claude generar contenuto errato o non SEO-optimizzato?<\/h3>\n<p>Ho implementato un sistema di &#8220;human-in-the-loop&#8221;: i post generati vengono salvati come <em>draft<\/em>, e require human approval prima di pubblicare. Inoltre, traccio un &#8220;review quality score&#8221; basato su lunghezza, keyword density, readability\u2014se il score \u00e8 troppo basso, il post va in una coda di revisione prioritaria. Niente viene pubblicato automaticamente senza revisione editoriale.<\/p>\n<h3>Posso usare le Abilities anche da Zapier o Make?<\/h3>\n<p>S\u00ec, tramite REST API. Se un&#8217;ability ha <code>show_in_rest: true<\/code>, \u00e8 esposta in <code>\/wp-json\/<\/code> e quindi raggiungibile da qualsiasi tool che supporta REST (Zapier, Make, n8n). Per\u00f2 MCP \u00e8 il metodo pi\u00f9 elegante perch\u00e9 mantiene il context della conversazione.<\/p>\n<h3>Quanto costa realmente automatizzare 50 post\/mese?<\/h3>\n<p>Con Claude Haiku (il modello pi\u00f9 economico), calcolo:<br \/>\n&#8211; Title generation: ~500 token per post = ~$0.025\/post<br \/>\n&#8211; Meta description: ~300 token = ~$0.015\/post<br \/>\n&#8211; Tags suggestion: ~200 token = ~$0.010\/post<br \/>\n&#8211; Total: ~$0.05\/post<br \/>\n&#8211; 50 post = ~$2.50\/mese<\/p>\n<p>Ovvio, se usi Opus \u00e8 10x di pi\u00f9, ma la quality \u00e8 proporzionalmente migliore per contenuti complessi.<\/p>\n<h2>Conclusione<\/h2>\n<p>WordPress 7.0 Abilities API non \u00e8 solo un&#8217;aggiunta\u2014\u00e8 un <strong>cambio di paradigma<\/strong> su come costruire editorial automation a scala. Nel 2026, questo non \u00e8 pi\u00f9 &#8220;optional nice-to-have&#8221;. \u00c8 <strong>competitive advantage<\/strong>.<\/p>\n<p>L&#8217;ho implementato per 3 client enterprise, e il risultato \u00e8 sempre lo stesso: <strong>80% riduzione nel tempo per content optimization, zero learning curve per editor existente<\/strong>. Si integra direttamente in WordPress, supporta m\u00faltipli AI provider, e sfrutta MCP per connessione diretta con Claude\/OpenAI\/Cursor.<\/p>\n<p>Se gestite un blog tecnico, un magazine, o una newsroom, cominciate a sperimentare oggi. <strong>Lasciate un commento se avete domande sul setup, o contattatemi per una sessione di implementation custom.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Come collegare Claude e OpenAI a WordPress 7.0 Abilities API per automatizzare titoli SEO, meta description e tag generation a scala. MCP Adapter + model chaining per editorial teams.<\/p>\n","protected":false},"author":1,"featured_media":2651,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_seopress_robots_primary_cat":"","_seopress_titles_title":"WordPress 7.0 Abilities API: Automazione Editorial AI | Guida Tecnica","_seopress_titles_desc":"Configura WordPress 7.0 Abilities API con Claude\/OpenAI per bulk meta generation, model chaining e automated SEO optimization. Tutorial completo con codice.","_seopress_robots_index":"","footnotes":""},"categories":[2],"tags":[293,1016,1017,1020,1018,797,1019,292],"class_list":["post-2650","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress","tag-abilities-api","tag-ai-automation","tag-claude-openai","tag-content-scaling","tag-editorial-workflow","tag-mcp-adapter","tag-seo-meta-generation","tag-wordpress-7-0"],"_links":{"self":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/posts\/2650","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/comments?post=2650"}],"version-history":[{"count":0,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/posts\/2650\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/media\/2651"}],"wp:attachment":[{"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/media?parent=2650"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/categories?post=2650"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/darioiannascoli.it\/blog\/wp-json\/wp\/v2\/tags?post=2650"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}