Files
claude-plugins/wordpress/skills/mcp-wpforms/SKILL.md
T
ealmeida e709130a1f docs(skills): adiciona mcp-wpforms — MCP dedicado ao WPForms + Form Abandonment
Documenta o novo MCP standalone /media/ealmeida/Dados/Dev/mcp-wpforms/ (12 tools,
descomplicar.pt + emanuelalmeida.pt): formularios, entries (listar/ler/contar/
marcar lida/estrela/spam), entries abandonadas com contexto capturado, settings
gerais e estado Akismet. Sem skill de conhecimento wpforms separada — esta skill
inclui o mapeamento completo de tabelas/options (unica documentacao).
2026-08-19 06:06:58 +01:00

157 lines
11 KiB
Markdown

---
name: mcp-wpforms
description: MCP dedicado multi-site (node stdio, ligação `wpforms` em ~/.omp/agent/mcp.json) para gerir o WPForms + WPForms Form Abandonment em descomplicar.pt e emanuelalmeida.pt via WP-CLI/SQL/SSH — formulários, entries (listar/ler/contar/marcar lida/estrela/spam), entries abandonadas com contexto capturado, settings gerais (captcha, licença), estado Akismet/spam. Site é um parâmetro em cada tool. Única documentação de mapeamento de dados WPForms — não existe skill de conhecimento separada. Usar quando "wpforms mcp", "entries wpforms", "formulário abandonado", "form abandonment", "spam wpforms", "akismet wpforms", "schema formulário wpforms", "wpforms_entries", "marcar entry como lida", "settings wpforms".
layer: wiki
---
# /mcp-wpforms — MCP dedicado multi-site ao WPForms + Form Abandonment
Projecto em `/media/ealmeida/Dados/Dev/mcp-wpforms/` (TypeScript, SDK MCP oficial, stdio). Sem
código PHP novo no WordPress: cada tool executa `wp db query` (SELECTs `JSON_OBJECT`/`JSON_ARRAYAGG`
e `UPDATE`s de 1 coluna), `wp option get` ou `wp post get` via `ssh server` sobre o WP-CLI já
instalado. `site` é um parâmetro em cada tool, não uma ligação fixa — como `mcp-wpfc`/`mcp-wpmeteor`.
Não existe skill de conhecimento "wpforms" separada (só o EMCP Tools referencia o ícone do addon
Form Abandonment, nada mais). **Esta skill é a única documentação do mapeamento de dados WPForms**
neste repositório — inclui aqui o schema das tabelas e das options que noutros MCPs (ex.
`wp-fastest-cache` ↔ `mcp-wpfc`) viveria numa skill "irmã".
## Âmbito
Formulários (leitura do schema), entries (listar/ler/contar, marcar lida/estrela/spam), entries
abandonadas do addon Form Abandonment, settings gerais (captcha, comportamento) e licença
(mascarada), estado Akismet. **Não cobre**: criação/edição do schema de campos de um formulário
(risco de corromper o formulário — fora de âmbito por desenho), reenvio de notificações de email,
tabelas de analytics/payments/tasks_meta/file_restrictions/protected_files do WPForms (não usadas
por nenhuma tool), o plugin `styler-for-wpforms` (inactivo em emanuelalmeida.pt).
## Sites conhecidos
Chamar `wpforms_list_sites` para a lista actual. Ao contrário de `mcp-wpfc`, **não** aceita um path
arbitrário — as queries SQL directas precisam do prefixo de tabela real do site (`table_prefix`),
que varia por instalação e não pode ser adivinhado com segurança. Só sites explicitamente
verificados são aceites. Confirmado ao vivo 19-08-2026 (`wp plugin list`, `wp config get
table_prefix`):
| Alias | Domínio | Path | `table_prefix` | WPForms | Form Abandonment |
|---|---|---|---|---|---|
| `descomplicar` | descomplicar.pt (produção) | `/home/ealmeida/public_html` | `wpah_` | 2.0.0.4, activo, licença Elite | 1.16.0, activo |
| `emanuelalmeida` | emanuelalmeida.pt | `/home/ealmeida/emanuelalmeida.pt` | `wpne_` | 2.0.0.4, activo | não instalado |
Akismet não está instalado em nenhum dos dois sites (`wpforms_get_spam_status` devolve
`akismet_plugin_installed: false`).
## Mapeamento de dados (confirmado por leitura directa do código-fonte e da BD)
### Formulários — `wp_posts` (post_type = `wpforms`)
O schema de cada formulário (campos + settings) vive em `post_content` como **texto JSON puro**
(não serializado PHP) — `{"fields": {...}, "settings": {...}, "id", "field_id", "search_terms",
"providers", "meta"}`. `fields` é um objecto indexado por field-id (não um array), cada entrada com
pelo menos `id`, `type`, `label`, `required`. As settings interessantes: `form_abandonment` (`"1"`
= activo), `form_abandonment_fields` (`""` = só guarda se tiver email/telefone, `"all"` = guarda
sempre), `form_abandonment_duplicates` (`"1"` = evita duplicados na mesma hora), `store_spam_entries`
(`"0"`/`"1"`), `akismet` (só existe se o plugin Akismet estiver instalado — nenhum dos 2 sites tem).
`wpforms_get_form_schema` extrai `fields` para uma lista simplificada (id/type/label/required/
description) e devolve `settings` completo tal-e-qual.
`wpforms_list_forms` usa `JSON_EXTRACT`/`JSON_UNQUOTE` directamente sobre `post_content` (sem
descodificar o JSON completo) para expor `form_abandonment_enabled`/`store_spam_entries` de forma
barata em massa — truque reutilizável para qualquer outra flag de settings sem custo de ler o
schema completo por formulário.
### Entries — tabelas custom `{prefix}wpforms_entries` / `entry_fields` / `entry_meta` / `logs`
`wpforms_entries`: `entry_id`, `form_id`, `post_id`, `user_id`, `status`, `type`, `viewed`
(tinyint), `starred` (tinyint), `fields` (longtext — JSON `{field_id: {name, value, id, type}}`,
os dados submetidos tal como o WPForms os mostra no admin), `meta` (longtext, **não usado** em
produção — sempre vazio, não confundir com a tabela `entry_meta`), `date`, `date_modified`,
`ip_address`, `user_agent`, `user_uuid`.
**`status` — valores confirmados por leitura do código-fonte** (não documentados centralmente pelo
próprio plugin, espalhados por `src/Pro/AntiSpam/SpamEntry.php` e pelo addon Form Abandonment):
- `''` (string vazia) — entry normal.
- `'abandoned'` — capturada pelo addon Form Abandonment (`WPFormsFormAbandonment\Plugin::process_entries`).
- `'spam'` — marcada como spam (`SpamEntry::ENTRY_STATUS`). Desmarcar spam **repõe sempre `''`**,
mesmo que a entry estivesse antes `abandoned`/`trash` — é o comportamento nativo de
`SpamEntry::set_as_not_spam`, replicado tal-e-qual em `wpforms_set_entry_spam`.
- `'trash'` — enviada para o lixo.
`entry_fields`: uma linha por (entry, field) para pesquisa/filtragem indexada — não usada por
nenhuma tool deste MCP (a coluna `fields` da própria entry já tem os mesmos dados, mais legível).
`entry_meta`: linhas `{entry_id, form_id, user_id, status, type, data, date}`. `type` observados:
`log` (histórico tipo "Entry read."), e — só em entries `abandoned` — `page_url`, `page_title`,
`page_id`, `url_referer`, `user_id` (o contexto capturado pelo Form Abandonment no momento do
abandono). `wpforms_list_abandoned_entries` faz o pivot destes 4 tipos para campos directos na
resposta.
`logs` (`{prefix}wpforms_logs`): log administrativo do plugin (título/mensagem/tipo), não exposto
por nenhuma tool — fora do âmbito pedido.
### Settings / licença — `wp_options`
`wpforms_settings` — array PHP nativo (`a:20:{...}`), **seguro usar `--format=json`** em leitura
(confirmado por `wp option get wpforms_settings --format=json`, sem dupla codificação). Contém
`captcha-provider` (`recaptcha`/`hcaptcha`/`turnstile`/vazio), chaves reCAPTCHA/hCaptcha/Turnstile,
`modern-markup`, etc. `wpforms_license` — também array nativo: `key`, `type` (`elite` em
descomplicar.pt), `is_expired`. `wpforms_get_settings` mascara sempre a chave de licença (só os
últimos 4 caracteres visíveis), nunca a devolve em claro.
## Tools (12)
| Tool | Read-only | Uso |
|---|---|---|
| `wpforms_list_sites` | sim | Aliases conhecidos, path, `table_prefix`, se tem Form Abandonment |
| `wpforms_list_forms` | sim | Todos os formulários (qualquer post_status): título, datas, `form_abandonment_enabled`, `store_spam_entries`, `entry_count` |
| `wpforms_get_form_schema` | sim | Schema completo de 1 formulário: campos simplificados + `settings` completo do `post_content` |
| `wpforms_list_entries` | sim | Entries com filtros `form_id`/`status` (normal/abandoned/spam/trash)/`date_from`/`date_to`, paginado — resumo sem `fields`/`meta` |
| `wpforms_get_entry` | sim | 1 entry completa: dados + `fields` submetidos + toda a `entry_meta` (log, contexto de abandono, razão de spam) |
| `wpforms_count_entries` | sim | Contagem por estado (`total`/`normal`/`abandoned`/`spam`/`trash`), agrupada por formulário |
| `wpforms_list_abandoned_entries` | sim | Entries `abandoned` com `fields` parciais + `page_url`/`page_title`/`url_referer`/`captured_user_id` (pivot de `entry_meta`) |
| `wpforms_get_settings` | sim | `wpforms_settings` (captcha, geral) + resumo de licença (chave sempre mascarada) |
| `wpforms_get_spam_status` | sim | Akismet instalado/activo, contagem global de spam, `store_spam_entries` + contagem de spam por formulário |
| `wpforms_set_entry_viewed` | não | `entries.viewed` — mirror da acção "Read"/"Unread" |
| `wpforms_set_entry_starred` | não | `entries.starred` — mirror da acção "Star"/"Unstar" |
| `wpforms_set_entry_spam` | não | `entries.status` — mirror exacto de `SpamEntry::set_as_spam`/`set_as_not_spam` (ver nota sobre repor `''`) |
## Segurança
Escritas restritas a `UPDATE` de 1 coluna (`viewed`, `starred`, `status`) sobre `entries`, sempre
precedidas de leitura para confirmar que a entry existe (`requireEntry`, lança erro claro se não).
Nunca escreve schema de formulário nem `post_content`. Identificadores de tabela/coluna validados
por `assertSafeIdentifier` (allowlist `[a-z0-9_]+`); filtros de texto livre (datas) escapados por
`sqlString`; texto SQL viaja sempre pelo stdin do processo SSH, nunca interpolado na linha de
comando remota. Site restrito à allowlist de `sites.ts` (sem path arbitrário) precisamente para
impedir uma query SQL correr contra um prefixo de tabela errado.
## Verificação
Construído e testado ponta-a-ponta contra produção 19-08-2026, via chamadas reais ao protocolo MCP
(stdio) e confirmação cruzada por SQL directo fora do MCP:
- `wpforms_list_forms` em `descomplicar` (1 formulário, "Pedido de Orçamento", 26 entries) e
`emanuelalmeida` (6 formulários, ex. "Formulário de contacto" com 17 entries) — confirmados por
`SELECT COUNT(*)`/`wp post list` directos antes de qualquer chamada ao MCP.
- `wpforms_count_entries` em `descomplicar`: `total=26, normal=11, abandoned=13, spam=0, trash=2` —
igual ao `GROUP BY status` feito directamente na BD.
- `wpforms_get_entry` (entry 37) e `wpforms_list_abandoned_entries` — campos submetidos (email,
telefone, empresa) e contexto de abandono (`page_url`, `page_title`) conferem com as colunas
`fields`/`entry_meta` lidas directamente.
- `wpforms_get_form_schema` (formulário 16446) — 157 campos extraídos correctamente do
`post_content`, incluindo `layout`/`pagebreak`.
- `wpforms_get_settings` — `captcha-provider: "recaptcha"` e licença `type: "elite"` com chave
mascarada, confirmados contra `wp option get wpforms_settings/wpforms_license --format=json`.
- `wpforms_get_spam_status` em ambos os sites — Akismet correctamente reportado como não instalado,
`store_spam_entries` extraído por `JSON_EXTRACT` a bater com o `post_content` bruto.
- Escrita testada e confirmada por SQL directo antes/depois: `wpforms_set_entry_viewed(descomplicar,
entry_id=37, viewed=true)` sobre uma entry já `viewed=1` — round-trip idempotente, valor
inalterado em produção, sem qualquer efeito colateral.
- Erros testados: formulário/entry inexistente devolvem mensagem clara (`isError: true`) em vez de
rebentar ou devolver dados parciais.
## Skills relacionadas
Nenhuma skill de conhecimento "wpforms" existe neste repositório — ver secção "Mapeamento de
dados" acima para o schema completo. `mcp-wpfc`/`mcp-wpmeteor` — mesmo padrão de MCP dedicado
multi-site sobre WP-CLI/SSH, para outros plugins do bundle.