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.
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 porfluentform_get_forme para rotular os valores emfluentform_get_submission.Submission::customQuery($attrs)— o MESMO método que o wp-admin usa para a listagem de entries; aceitaform_id,entry_type(nome real do parâmetro de estado — nãostatus;'favorites'é tratado à parte viais_favourite=1),search,date_range: [from,to],sort_by.SubmissionService::find($id)— detalhe completo de 1 entry. EFEITO SECUNDÁRIO REAL DO PLUGIN: marca automaticamenteunread→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; disparado_action('fluentform/after_submission_status_update', ...).SubmissionService::toggleIsFavorite($id)— toggle puro, devolve[mensagem, novo_estado].SubmissionService::resources(['form_id'=>,'counts'=>true])→Submission::countByGroup($formId)— contagensunread/read/spam/trashed/all(exclui trashed)/favorites.GlobalSettingsService::get(['key'=>[...]])— só lê chaves com prefixofluentform_/_fluentform_/fluentform-/_fluentform-(allowlist própria do plugin); usado porfluentform_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()), nuncawhereRaw/interpolação directa. fluentform_get_settingssó lê chaves com o prefixo allowlisted pelo próprioGlobalSettingsService— 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_submissiondevolve 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 buildlimpo (tsc, sem erros).- Smoke test stdio:
initialize+tools/listdevolveu os 9 tools esperados. - Leituras reais confirmadas por SQL directo fora do MCP:
fluentform_list_formsemdescomplicar(3 formulários) estarter(2 formulários, ambospublished);fluentform_get_formno formulário #3 (descomplicar) devolveu 8 campos incluindo um campo FluentBooking (fcal_booking);fluentform_list_submissionsno formulário #3 (descomplicar, 1 entry,status:read);fluentform_count_submissionsno mesmo formulário ({unread:0,read:1,spam:0,trashed:0,all:1,favorites:0});fluentform_get_settingsemdescomplicardevolveu_fluentform_global_form_settings/fluentform_global_modules_status/_fluentform_installed_versioncorrectos. fluentform_get_submissionna entry #1 (descomplicar): 7 campos rotulados correctamente (nome, email, empresa, cargo, data de reserva FluentBooking, 2 checkboxes), datas devolvidas como stringY-m-d H:i:s(não o objecto Carbon cru).- Escrita 1 (idempotente):
fluentform_update_submission_statusna entry #1 comstatus:"read"(valor já-corrente) — devolveu{entry_id:1,status:"read"}, confirmado por SQL directo questatus/is_favouriteficaram inalterados (sóupdated_atmudou, efeito normal de qualquer update). - Escrita 2 (round-trip):
fluentform_toggle_favoritena entry #1 chamado sequencialmente duas vezes — 1ª chamada devolveuis_favourite:true(confirmadois_favourite=1por SQL directo), 2ª chamada devolveuis_favourite:false(confirmadois_favourite=0por SQL directo, estado final idêntico ao inicial). - Tratamento de erro confirmado:
fluentform_get_formcomform_id:9999devolveuisError:truecom "Formulário #9999 não encontrado.";fluentform_list_formscomsite:"unknown-site"devolveuisError:truelistando os aliases conhecidos.
Skills relacionadas
mcp-fluent-crm— mesmo mecanismowp evalsobre 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ãositecomo parâmetro em cada tool.