--- name: mcp-rank-math description: MCP dedicado (node stdio, ligação `rank-math` em ~/.omp/agent/mcp.json) para o Rank Math SEO/Rank Math Pro em descomplicar.pt via WP-CLI/SSH. 27 tools tipadas — meta de post/taxonomia, schema markup, redirects, módulos, sitemap, 404 monitor, analytics GSC, IndexNow, validação SEO ao vivo. Preferir a este MCP em vez dos snippets manuais `wp eval`/`wp db query` da skill `/rank-math` sempre que a operação já esteja coberta aqui. Usar quando "rank math", "meta SEO", "schema markup", "redirects seo", "404 monitor", "módulos rank math", "seo score", "sitemap rank math", "indexnow", "validar seo página". layer: wiki --- # /mcp-rank-math — MCP dedicado Rank Math SEO / Rank Math Pro Projecto em `/media/ealmeida/Dados/Dev/mcp-rank-math/` (TypeScript, SDK MCP oficial, stdio). Substitui a execução manual dos comandos `wp eval`/`wp db query`/`wp option patch` documentados na skill `/rank-math` por 27 tools tipadas, com validação de input e capability boundaries claras. **Ambas as skills coexistem** — `/rank-math` continua a ser a referência para o que ainda não tem tool dedicada (ver "Fora do âmbito" abaixo); este MCP é o caminho preferido para tudo o resto. ## Porquê duas pontes internas (SQL vs PHP-eval) Rank Math guarda a maior parte dos dados **serializados em PHP** — a tabela `rank_math_redirections.sources`, quase todas as `rank-math-options-*`, e qualquer meta de post/term com valor array (`rank_math_robots`, `rank_math_schema_*`). SQL directo lê o blob serializado em bruto; só o WordPress core (`get_option()`, `get_post_meta()`, `maybe_unserialize()`) descodifica isso correctamente. Por isso o MCP usa duas pontes: - **SQL directo** (`wp db query --raw`) — só contra tabelas confirmadas 100% planas: `posts`/`postmeta` (joins de auditoria), `rank_math_404_logs`, `rank_math_analytics_gsc`. - **`wp eval-file -`** (PHP inteiro recebido via STDIN, nunca interpolado na linha de comando) — tudo o resto: meta com arrays, schema, redirects (via `\RankMath\Redirections\DB`, para disparar os side-effects certos, nunca `INSERT`/`UPDATE` SQL directo nessa tabela), módulos, options com `wp option patch` (nunca `wp option update`, que substitui a option inteira e apaga as outras sub-chaves). Valores dinâmicos (títulos, descrições, schema JSON) nunca são interpolados directamente numa string PHP: viajam como JSON (`json_decode(...)` no lado PHP), evitando tanto injecção como o bug clássico de `"$5 off"` a ser lido como interpolação de variável dentro de uma string PHP de aspas duplas. ## Gotcha real de produção — `rank_math_modules` A option tem **chaves não-sequenciais** (buracos de remoções antigas de módulos sem `array_values()`). `wp option get --format=json` faz `json_encode()` directo do array PHP subjacente — um array PHP com chaves não-consecutivas serializa como **objecto** JSON (`{"0":"a","3":"b"}`), não array. O MCP normaliza isto internamente (`rm_get_plugin_status`, `rm_list_modules`); se algum dia se ler `rank_math_modules` directamente via `wp option get` fora do MCP, **não assumir que a saída é sempre um array JSON**. ## Tools (27) **Estado/auditoria:** `rm_get_plugin_status` (versões Free/Pro, licença, nº módulos activos), `rm_audit_missing_meta`, `rm_audit_seo_scores`, `rm_get_top_404s`, `rm_clear_404_logs` (`confirm: true` para apagar tudo) **Meta de post:** `rm_get_post_meta`, `rm_update_post_meta` (parcial — `null` num campo apaga-o), `rm_bulk_fill_missing_meta` (`dry_run: true` por omissão — só lista, não escreve) **Meta de taxonomia:** `rm_get_term_meta`, `rm_update_term_meta` **Schema markup:** `rm_get_post_schema`, `rm_set_post_schema` (`schema_type` alfanumérico — "Article", "FAQPage", "Product", "LocalBusiness", etc.), `rm_delete_post_schema` (omitir `schema_type` apaga todos — requer `confirm: true`) **Options/módulos:** `rm_get_option`, `rm_set_option` (sempre `wp option patch` internamente — nunca perde sub-chaves irmãs), `rm_list_modules`, `rm_toggle_module` **Redirects:** `rm_list_redirects`, `rm_create_redirect`, `rm_update_redirect`, `rm_delete_redirects` **Sitemap:** `rm_regenerate_sitemap`, `rm_list_sitemap_urls` (HTTP directo ao `sitemap_index.xml` público — sem SSH, reflecte exactamente o que o Google vê) **IndexNow:** `rm_submit_indexnow` (HTTP directo à API do IndexNow; só lê a chave via WP-CLI) **Analytics:** `rm_get_analytics_overview` — degrada com `available:false` se o módulo/tabela GSC não existir, em vez de erro **Backup/validação:** `rm_export_seo_snapshot` (devolve os dados; não escreve ficheiro no servidor), `rm_validate_live_seo` (fetch directo ao URL publicado — título, meta description, canonical, robots, OG tags, tipos JSON-LD tal como renderizados, sem SSH) ## Segurança - `rm_clear_404_logs` sem `older_than_days` e `rm_delete_post_schema` sem `schema_type` exigem `confirm: true`. - `rm_set_option` nunca aceita substituir a option inteira — só `wp option patch` num caminho de sub-chave. - `rm_submit_indexnow` e o ramo de escrita de `rm_update_redirect`/`rm_create_redirect` têm efeito público real (notificação a motores de busca, redireccionamento de tráfego real) — confirmar intenção antes de invocar em massa. ## Fora do âmbito (usar `/rank-math` para estes) WooCommerce SEO em massa (categorias de produto, atributos — os padrões `wp eval` já documentados na skill `/rank-math` §9 continuam válidos), News/Video Sitemap (meta dedicada por post, §10), hooks/filtros PHP persistentes (§11), migração Yoast→Rank Math (§migração), provisioning inicial de um site novo (§13 — sequência de setup, não uma operação repetível), gestão de licença/ligação de conta Pro (ver skill `/full-cliente`). ## Verificação Construído e testado ponta-a-ponta contra produção 19-08-2026: leituras confirmadas com dados reais (distribuição de scores 383 bons/36 ok/116 fracos, 404s reais, 21 módulos activos, sitemap com 9 entradas, validação ao vivo de uma página publicada com título/description/canonical/OG idênticos ao que `rm_get_post_meta` devolve para o mesmo post). Escritas testadas sem alterar dados reais: `rm_set_option`/`rm_update_post_meta` escreveram o mesmo valor já existente (no-op real, confirmado por leitura pós-escrita); ciclo completo `rm_create_redirect`→ `rm_delete_redirects` com um redirect de teste, confirmado removido a seguir. Um bug real (`rank_math_modules` object-vs-array) foi apanhado neste smoke test e corrigido antes de considerar o MCP pronto — ver README do projecto. ## Nota `rm_get_post_meta`/`rm_get_term_meta` num ID inexistente devolvem campos vazios em vez de erro (comportamento do próprio `get_post_meta()`/`get_term_meta()` do WordPress core, que não avisa para ID inválido) — confirmar a existência do post/termo antes de interpretar "tudo vazio" como "sem meta definida".