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).
157 lines
11 KiB
Markdown
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.
|