Files
claude-plugins/wordpress/skills/mcp-fluentform/SKILL.md
T
ealmeida addd556a07 docs: skill mcp-fluentform — MCP FluentForms (formulários, submissions, contagens, settings)
Documenta o MCP dedicado mcp-fluentform (descomplicar.pt + starter.descomplicar.pt):
9 tools sobre a camada de serviço oficial do plugin (FormService/
SubmissionService/GlobalSettingsService), mapeamento de tabelas custom
fluentform_*, gotchas confirmados ao vivo (serialização de datas Carbon-like,
race condition em escritas paralelas dependentes) e evidência de verificação
ponta-a-ponta contra produção/staging.
2026-08-19 06:08:01 +01:00

13 KiB

name, description, layer
name description layer
mcp-fluentform MCP dedicado multi-site (node stdio, ligação `fluentform` em ~/.omp/agent/mcp.json) para o FluentForms 6.2.12 (WPManageNinja) em descomplicar.pt (produção) e starter.descomplicar.pt (staging) — formulários, submissions (entries), contagens por estado e settings gerais, via WP-CLI/SSH sobre a camada de serviço oficial do plugin (FormService/SubmissionService/GlobalSettingsService). Escritas limitadas a estado da entry (unread/read/spam/trashed) e favorita/star — nunca schema de formulário nem integrações de terceiros. Usar quando "fluentforms", "formulários wordpress", "entries fluentform", "submissions fluentform", "marcar entry como lida", "favoritar entry", "spam fluentform", "trash entry fluentform", "contagem de submissions", "settings fluentforms", ou qualquer execução (não estratégia) sobre o FluentForms. wiki

/mcp-fluentform — MCP dedicado FluentForms (multi-site)

Projecto em /media/ealmeida/Dados/Dev/mcp-fluentform/ (TypeScript, SDK MCP oficial, stdio). Não existe skill de conhecimento separada sobre o FluentForms — esta skill é a ÚNICA documentação, incluindo o mapeamento de dados (tabelas/serviços) que noutros casos vive numa skill "irmã" (ver fluent-crm↔mcp-fluent-crm para esse padrão).

Achado relevante desta sessão: o FluentForms 6.2.12 já traz o seu próprio módulo MCP interno (app/Modules/MCP/, abilities fluentform/list-submissions, fluentform/update-submission-status, etc., via WP Abilities API), mas só fica acessível externamente se o site tiver o plugin mcp-adapter ou fluent-toolkit activo e o toggle "MCP" ligado nas definições do FluentForms — nenhuma das duas condições está preenchida hoje nos dois sites. Este MCP não depende disso: chama directamente a camada de serviço PHP subjacente (FormService/SubmissionService/ GlobalSettingsService), a mesma que o módulo MCP nativo do plugin também usa por baixo.

Sites conhecidos

Chamar fluentform_list_sites para a lista actual. Confirmados ao vivo 19-08-2026, FluentForms 6.2.12 active nos dois — são os únicos dois sites do bundle com o plugin activo (wp plugin list):

Alias Domínio Prefixo de tabela Formulários Submissions
descomplicar descomplicar.pt (produção) wpah_ 3 (Contact Form Demo unpublished, Subscription Form unpublished, Blank Form (#3) published) 1 (form #3, status read)
starter starter.descomplicar.pt (staging) wpbk_ 2 (Contact Form Demo, Subscription Form, ambos published) 0

Também aceita um path absoluto directamente (/home/ealmeida/<site>) para sites fora desta lista, sem exigir rebuild do MCP — mas confirmar primeiro que o FluentForms está activo lá (wp plugin list --allow-root --path=<path>).

Mapeamento de dados

FluentForms não expõe WP-CLI CRUD (wp fluentform só tem activate_license/license_status/ stats). Toda a leitura/escrita deste MCP passa pela camada de serviço PHP oficial do plugin, via wp eval — nunca SQL directo, nunca wp option update.

Tabelas custom (prefixo de tabela varia por site, ver acima; resolvido internamente pelo ORM do plugin, nunca hardcoded em TypeScript):

Tabela (sem prefixo) Papel
fluentform_forms formulários — title, status (published/unpublished), type, form_fields (JSON do schema), has_payment
fluentform_submissions entries — form_id, response (JSON das respostas), status (unread/read/spam/trashed), is_favourite, serial_number, user_id, payment_*
fluentform_submission_meta meta por submission (ex. _entry_uid_hash para link público)
fluentform_entry_details valores desnormalizados por campo (usado por relatórios/exportação)
fluentform_form_meta meta por formulário (settings, notificações, integrações)
fluentform_form_analytics contadores de vistas/conversão por formulário
fluentform_logs log de eventos por submission/formulário

Classes de serviço oficiais usadas por este MCP (FluentForm\App\Services\* + FluentForm\App\Models\*, todas instanciáveis directamente com new, sem container DI):

  • Form::query() — listagem/filtro de formulários (orderBy, where('title','LIKE',...), where('status',...), ->paginate($perPage,['*'],'page',$page)).
  • FormService::getInputsAndLabels($formId) — schema de campos (inputs/labels), usado por fluentform_get_form e para rotular os valores em fluentform_get_submission.
  • Submission::customQuery($attrs) — o MESMO método que o wp-admin usa para a listagem de entries; aceita form_id, entry_type (nome real do parâmetro de estado — não status; 'favorites' é tratado à parte via is_favourite=1), search, date_range: [from,to], sort_by.
  • SubmissionService::find($id) — detalhe completo de 1 entry. EFEITO SECUNDÁRIO REAL DO PLUGIN: marca automaticamente unread→read (mesmo comportamento de abrir a entry no wp-admin) — documentado na descrição da tool, não é bug deste MCP.
  • SubmissionService::updateStatus(['entry_id'=>,'status'=>]) — unread/read/spam/trashed; dispara do_action('fluentform/after_submission_status_update', ...).
  • SubmissionService::toggleIsFavorite($id) — toggle puro, devolve [mensagem, novo_estado].
  • SubmissionService::resources(['form_id'=>,'counts'=>true]) → Submission::countByGroup($formId) — contagens unread/read/spam/trashed/all (exclui trashed)/favorites.
  • GlobalSettingsService::get(['key'=>[...]]) — só lê chaves com prefixo fluentform_/_fluentform_/fluentform-/_fluentform- (allowlist própria do plugin); usado por fluentform_get_settings.

Arquitectura — wp eval sobre a camada de serviço, nunca SQL/WP-CLI directo

Corpo PHP fixo (nunca construído a partir de texto do chamador) + dados de entrada (JSON) viajam em base64 dentro de uma variável de ambiente remota, nunca interpolados como texto dinâmico no comando SSH — mesmo padrão de mcp-fluent-crm/src/wp-bridge.ts (runPhp()), adaptado a multi-site: o path WordPress é um parâmetro por chamada (src/sites.ts), não uma config fixa.

Gotcha de datas — não replicar sem testar primeiro: $model->created_at/updated_at não devolvem uma string — devolvem um objecto FluentForm\Framework\Support\DateTime (Carbon-like). json_encode() directo sobre esse objecto produz {"date":"...","timezone_type":3,"timezone":"..."} em vez de uma string, porque o serializeDate() custom do Model.php do plugin só é aplicado em toArray()/toJson(), nunca num json_encode() PHP nativo isolado. Corrigido em todos os pontos deste MCP com (string) $form->created_at — o DateTime do framework implementa __toString() devolvendo Y-m-d H:i:s correctamente.

Gotcha de race condition — confirmado ao vivo durante a construção: duas chamadas de escrita dependentes (ex. dois fluentform_toggle_favorite seguidos, para testar round-trip) NUNCA podem correr em paralelo — cada wp eval é um processo PHP efémero independente; disparar dois concorrentes faz ambos lerem o mesmo estado "antigo" e o resultado final fica indeterminado (visto ao vivo: 1º toggle devolveu is_favourite:true, mas a leitura SQL imediatamente a seguir mostrou 0 porque o 2º toggle, corrido em paralelo, tinha lido o 0 original em vez do true já escrito pelo 1º). Corrigido correndo sequencialmente; qualquer teste/uso futuro deste MCP com múltiplas escritas na mesma entry deve aguardar o resultado de cada chamada antes da seguinte.

Tools (9)

Tool Read-only Uso
fluentform_list_sites sim Aliases conhecidos, path WP, prefixo de tabela
fluentform_list_forms sim search/status(published|unpublished)/sort_by/paginação — id, título, estado, tipo, has_payment, datas
fluentform_get_form sim Detalhe de 1 formulário + schema de campos (key/label/element) — necessário para saber as chaves esperadas em fluentform_get_submission
fluentform_list_submissions sim Entries de UM formulário (form_id obrigatório) — filtros status(unread|read|spam|trashed|favorites)/search/date_from+date_to(YYYY-MM-DD)/sort_by, paginação
fluentform_get_submission efeito secundário Detalhe completo de 1 entry (campos rotulados). Marca unread→read automaticamente (comportamento real do plugin)
fluentform_count_submissions sim Contagens por estado de um formulário: unread/read/spam/trashed/all/favorites
fluentform_update_submission_status não unread/read/spam/trashed. trashed é soft-delete (reversível chamando de novo com outro estado)
fluentform_toggle_favorite não Toggle puro — chamar duas vezes seguidas (sequencialmente!) devolve ao estado original
fluentform_get_settings sim Options com allowlist fluentform_/_fluentform_ — por omissão: _fluentform_global_form_settings (layout/misc), fluentform_global_modules_status (integrações ligadas/desligadas, nunca credenciais), _fluentform_installed_version. Aceita keys para ler chaves específicas dentro do mesmo allowlist

Todas as tools exigem site como primeiro parâmetro.

Decisão deliberada: sem escrita de schema/integrações

Nenhuma tool cria/edita formulários, campos, notificações ou integrações de terceiros (Mailchimp/Slack/etc). Razão (do enunciado desta tarefa): risco alto, fora do âmbito de um MCP de gestão de entries — mudar form_fields/integrações parte a experiência de submissão pública do site sem forma fácil de detectar isso a partir de fora. Gerir formulários/integrações via wp-admin (FluentForms → Forms/Settings).

Segurança

  • Nenhuma tool constrói SQL ou nomes de tabela a partir de input do chamador — todas as queries passam por métodos do ORM do próprio plugin (Form::query(), Submission::customQuery(), Model::find()), nunca whereRaw/interpolação directa.
  • fluentform_get_settings só lê chaves com o prefixo allowlisted pelo próprio GlobalSettingsService — não há forma de ler uma option arbitrária fora do namespace FluentForms através deste MCP.
  • Corpo PHP + payload sempre em base64 dentro de env var remota — nunca interpolados como texto no comando SSH (ver src/wp-bridge.ts).
  • fluentform_get_submission devolve valores de campos submetidos pelo público (texto não confiável) sem qualquer fencing adicional — ao contrário do módulo MCP nativo do plugin (que aplica marcadores [[UNTRUSTED_USER_INPUT]]), porque este MCP é de uso interno da equipa, não exposto a um agente autónomo com acesso à internet pública. Reavaliar se o uso mudar.

Verificação

Construído e testado ponta-a-ponta contra produção e staging em 19-08-2026, via protocolo MCP real (stdin/stdout, não só chamadas SSH soltas):

  • npm run build limpo (tsc, sem erros).
  • Smoke test stdio: initialize + tools/list devolveu os 9 tools esperados.
  • Leituras reais confirmadas por SQL directo fora do MCP: fluentform_list_forms em descomplicar (3 formulários) e starter (2 formulários, ambos published); fluentform_get_form no formulário #3 (descomplicar) devolveu 8 campos incluindo um campo FluentBooking (fcal_booking); fluentform_list_submissions no formulário #3 (descomplicar, 1 entry, status:read); fluentform_count_submissions no mesmo formulário ({unread:0,read:1,spam:0,trashed:0,all:1,favorites:0}); fluentform_get_settings em descomplicar devolveu _fluentform_global_form_settings/fluentform_global_modules_status/ _fluentform_installed_version correctos.
  • fluentform_get_submission na entry #1 (descomplicar): 7 campos rotulados correctamente (nome, email, empresa, cargo, data de reserva FluentBooking, 2 checkboxes), datas devolvidas como string Y-m-d H:i:s (não o objecto Carbon cru).
  • Escrita 1 (idempotente): fluentform_update_submission_status na entry #1 com status:"read" (valor já-corrente) — devolveu {entry_id:1,status:"read"}, confirmado por SQL directo que status/is_favourite ficaram inalterados (só updated_at mudou, efeito normal de qualquer update).
  • Escrita 2 (round-trip): fluentform_toggle_favorite na entry #1 chamado sequencialmente duas vezes — 1ª chamada devolveu is_favourite:true (confirmado is_favourite=1 por SQL directo), 2ª chamada devolveu is_favourite:false (confirmado is_favourite=0 por SQL directo, estado final idêntico ao inicial).
  • Tratamento de erro confirmado: fluentform_get_form com form_id:9999 devolveu isError:true com "Formulário #9999 não encontrado."; fluentform_list_forms com site:"unknown-site" devolveu isError:true listando os aliases conhecidos.

Skills relacionadas

  • mcp-fluent-crm — mesmo mecanismo wp eval sobre camada de serviço oficial, para contactos e campanhas em descomplicar.pt; FluentCRM tem automações que disparam a partir de submissões FluentForm (FluentFormSubmissionTrigger).
  • mcp-fluent-smtp — transporte de email usado pelas notificações de submissão do FluentForms.
  • mcp-wpfc / mcp-wpmeteor — outros MCPs multi-site do mesmo bundle, padrão site como parâmetro em cada tool.