feat(wordpress): novo MCP mcp-rank-math - escrita/gestao Rank Math SEO

27 tools via WP-CLI/SSH (bridge SQL para tabelas planas + wp eval-file
para dados serializados/side-effects): meta de post/taxonomia, schema
markup, redirects (via RankMath\Redirections\DB), modulos, sitemap,
404 monitor, analytics GSC, IndexNow, validacao SEO ao vivo por HTTP.

Cobre exactamente o que o MCP oficial rank-math-mcp (abilities
rank-math/* via WP Abilities API, documentado nesta sessao por outro
agente em rank-math/SKILL.md secao 17) nao cobre: esse e read-only/
analise (audit-site-seo, get-post-schema, scores, AI Visibility); este
e escrita/gestao (redirects, modulos, 404s, sitemap, options). Os dois
coexistem - rank-math/SKILL.md secao 17.4 actualizada com tabela de
quando usar qual dos tres caminhos (rank-math-mcp / mcp-rank-math /
WP-CLI).

Testado ponta-a-ponta em producao 19-08-2026: leituras com dados reais
(383 posts score bom, 21 modulos activos, 3 redirects, sitemap 9
entradas) e escritas verificadas sem mutar dados (no-op em option/meta
existentes, ciclo completo create+delete de um redirect de teste). Bug
real apanhado no smoke test: rank_math_modules tem chaves nao-
sequenciais na BD -> json_encode serializa como objecto, nao array -
normalizado em normalizeStringArray() antes de considerar pronto.
This commit is contained in:
2026-08-19 05:53:53 +01:00
parent ea051c273f
commit 93c68f7e58
3 changed files with 129 additions and 7 deletions
+113
View File
@@ -0,0 +1,113 @@
---
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".