From e709130a1f7fd15e792f69cbc20ac0766cda8d92 Mon Sep 17 00:00:00 2001 From: Emanuel Almeida Date: Wed, 19 Aug 2026 06:06:58 +0100 Subject: [PATCH] =?UTF-8?q?docs(skills):=20adiciona=20mcp-wpforms=20?= =?UTF-8?q?=E2=80=94=20MCP=20dedicado=20ao=20WPForms=20+=20Form=20Abandonm?= =?UTF-8?q?ent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- wordpress/skills/mcp-wpforms/SKILL.md | 156 ++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 wordpress/skills/mcp-wpforms/SKILL.md diff --git a/wordpress/skills/mcp-wpforms/SKILL.md b/wordpress/skills/mcp-wpforms/SKILL.md new file mode 100644 index 0000000..15f44de --- /dev/null +++ b/wordpress/skills/mcp-wpforms/SKILL.md @@ -0,0 +1,156 @@ +--- +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.