Estado geral, connections com segredos sempre mascarados, logs de email com filtros/paginacao, detalhe de log, estatisticas de envio, reenvio controlado (confirm:true). Documenta tambem o mapeamento de dados do plugin (unica fonte, nao existe skill de conhecimento separada) e a decisao deliberada de nao expor escrita de configuracao de connections.
195 lines
13 KiB
Markdown
195 lines
13 KiB
Markdown
---
|
|
name: mcp-fluent-smtp
|
|
description: MCP dedicado multi-site (node stdio, ligação `fluent-smtp` em ~/.omp/agent/mcp.json) para FluentSMTP 2.3.1 (WPManageNinja) em descomplicar.pt, starter.descomplicar.pt e emanuelalmeida.pt — controla a entrega de email do site, tratado como superfície de alto risco. Estado geral, connections configuradas (segredos SEMPRE mascarados), logs de email com filtros/paginação, detalhe de log, estatísticas de envio, reenvio controlado de 1 email falhado (confirm:true obrigatório). NENHUMA escrita de configuração de connections. Usar quando "fluentsmtp", "fluent smtp", "email não enviado", "logs de email wordpress", "reenviar email falhado", "connection smtp wordpress", "fallback connection smtp", "estatísticas de envio de email", ou qualquer diagnóstico/consulta de entrega de email nestes 3 sites.
|
|
layer: wiki
|
|
---
|
|
|
|
# /mcp-fluent-smtp — MCP dedicado FluentSMTP (multi-site)
|
|
|
|
Projecto em `/media/ealmeida/Dados/Dev/mcp-fluent-smtp/` (TypeScript, SDK MCP oficial, stdio).
|
|
Não existe skill de conhecimento separada sobre o FluentSMTP — esta skill é a ÚNICA
|
|
documentação, incluindo o mapeamento de dados (schema/option) que noutros casos vive numa skill
|
|
"irmã" (ver `wp-fastest-cache`↔`mcp-wpfc` ou `fluent-crm`↔`mcp-fluent-crm` para esse padrão).
|
|
|
|
**FluentSMTP controla a entrega de email do site inteiro — tratado como superfície de alto
|
|
risco.** Por isso este MCP é deliberadamente maioritariamente READ, e a única escrita (reenvio de
|
|
1 email) exige `confirm:true` e nunca toca em configuração de connection.
|
|
|
|
## Sites conhecidos
|
|
|
|
Chamar `fluentsmtp_list_sites` para a lista actual. Confirmados ao vivo 19-08-2026, FluentSMTP
|
|
2.3.1 active nos três:
|
|
|
|
| Alias | Domínio | Prefixo de tabela | Connections | Fallback |
|
|
|---|---|---|---|---|
|
|
| `descomplicar` | descomplicar.pt (produção) | `wpah_` | 2 (Elastic Email + SMTP directo porta 25, default) | configurado mas **órfão** — aponta para um ID que já não existe em `connections` |
|
|
| `starter` | starter.descomplicar.pt (staging) | `wpbk_` | 1 (SMTP) | não configurado (chave ausente) |
|
|
| `emanuelalmeida` | emanuelalmeida.pt | `wpne_` | 1 (SMTP) | presente mas vazio (não configurado) |
|
|
|
|
Também aceita um path absoluto directamente (`/home/ealmeida/<site>`) para sites fora desta lista,
|
|
sem exigir rebuild do MCP — mas nesse caso confirmar primeiro que o FluentSMTP está activo lá.
|
|
|
|
**Nota:** `emanuelalmeida.pt` tem também um mu-plugin próprio `descomplicar-fluentsmtp-auto-retry`
|
|
(descoberto via `wp plugin list`, must-use) — fora do âmbito deste MCP, mas relevante para
|
|
diagnóstico de falhas nesse site especificamente (pode já estar a reenviar automaticamente antes
|
|
de este MCP ser chamado).
|
|
|
|
## Mapeamento de dados (schema)
|
|
|
|
FluentSMTP não expõe WP-CLI custom commands nem API REST própria. Toda a leitura/escrita passa
|
|
pela camada de serviço PHP do plugin, via `wp eval` (ver secção Arquitectura).
|
|
|
|
**Config (`wp_options.fluentmail-settings`)** — option nativa PHP array (não string JSON dentro de
|
|
string; `wp option get --format=json` funciona correctamente sem dupla-codificação):
|
|
|
|
```
|
|
{
|
|
connections: {
|
|
"<connId hex32>": {
|
|
title: string,
|
|
provider_settings: {
|
|
provider: "smtp"|"ses"|"mailgun"|"sendgrid"|"sendinblue"|"sparkpost"|"pepipost"|
|
|
"postmark"|"elasticmail"|"gmail"|"outlook"|"tosend"|"cloudflare",
|
|
sender_name, sender_email, force_from_name, force_from_email,
|
|
// campo secreto por provider (ver mapa completo abaixo) — sempre AES-256-CTR
|
|
// cifrado (LOGGED_IN_KEY/SALT) + base64 quando use_encrypt="yes"
|
|
password | secret_key | api_key | client_secret,
|
|
host, port, auth, encryption, auto_tls, return_path, // só provider=smtp
|
|
key_store: "db", // ou "config" se a credencial vier de wp-config.php (não visto ainda)
|
|
}
|
|
}, ...
|
|
},
|
|
mappings: { "<email>": "<connId>" }, // roteamento por remetente
|
|
misc: {
|
|
default_connection: "<connId>",
|
|
fallback_connection: "<connId>", // PODE ficar órfão após apagar a connection alvo
|
|
log_emails: "yes"|"no",
|
|
log_saved_interval_days: "<N>", // retenção do log em dias
|
|
disable_fluentcrm_logs: "yes"|"no",
|
|
send_as_text: "yes"|"no",
|
|
simulate_emails: "yes"|"no", // modo simulação — nunca envia de verdade
|
|
},
|
|
use_encrypt: "yes"|"", // se "", segredos ficam em claro na option (visto em starter)
|
|
test: "<blob cifrado usado como canário de decifração>",
|
|
}
|
|
```
|
|
|
|
Mapa provider→campo secreto (replicado de `helpers.php`, `fluentMailGetSettings`/`SetSettings` —
|
|
manter sincronizado se o plugin actualizar):
|
|
`smtp→password`, `ses→secret_key`, `mailgun/sendgrid/sendinblue/sparkpost/pepipost/postmark/
|
|
elasticmail/tosend/cloudflare→api_key`, `gmail/outlook→client_secret`.
|
|
|
|
**Logs (`wp<prefixo>_fsmpt_email_logs`, prefixo `FLUENT_MAIL_DB_PREFIX`='fsmpt_')** — tabela
|
|
custom, prefixo de tabela WordPress **varia por site** (ver tabela acima):
|
|
|
|
| Coluna | Tipo | Nota |
|
|
|---|---|---|
|
|
| `id` | int PK | |
|
|
| `to` / `headers` / `attachments` | longtext | PHP-serializado — precisa `maybe_unserialize()` |
|
|
| `from`, `subject` | varchar(255) | |
|
|
| `body` | longtext | HTML/texto completo do email |
|
|
| `status` | varchar(20) | `pending`\|`sent`\|`failed` |
|
|
| `response` | text | serializado — erro do provider quando `failed` |
|
|
| `extra` | text | serializado — `provider`, `send_time_ms`, histórico `resends[]` (cada um com `at`,`to`,`by`,`sent`,`ms`) |
|
|
| `retries` / `resent_count` | int | tentativas automáticas vs reenvios manuais |
|
|
| `created_at` / `updated_at` | timestamp | índice composto `(created_at, status)` |
|
|
|
|
## Arquitectura — `wp eval` sobre a camada de serviço oficial, nunca SQL/`wp option` directo
|
|
|
|
Três razões concretas para este plugin (documentadas em `src/wp-bridge.ts`):
|
|
|
|
1. **Prefixo de tabela varia por site** (`wpah_`/`wpbk_`/`wpne_` confirmados ao vivo) —
|
|
`$wpdb->prefix` resolve isto dentro do PHP; não faz sentido rastrear isto em TS por site.
|
|
2. **Segredos cifrados** — `fluentMailGetSettings()` já decifra/mascarara consistentemente; não
|
|
vale a pena reimplementar AES-256-CTR + LOGGED_IN_KEY/SALT em TypeScript.
|
|
3. **Reenvio tem de passar pelo mecanismo real** (`Logger::resendEmailFromLog()`) — conversão de
|
|
anexos, cabeçalhos, `resent_count`, histórico em `extra` — nunca um UPDATE SQL a simular isto.
|
|
|
|
Corpo PHP + dados de entrada (JSON) viajam em base64 dentro de uma variável de ambiente remota,
|
|
nunca interpolados no comando SSH — **nenhum ficheiro é escrito/apagado no servidor**, o que
|
|
evitaria deliberadamente o GATE 5.1 de mutação de infra-estrutura. Ver `src/wp-bridge.ts`
|
|
(`runPhp()`) e `src/php.ts` (os 6 corpos PHP, um por tool).
|
|
|
|
**Gotcha de paginação — não replicar sem testar primeiro:** `Logger::get()`/
|
|
`QueryBuilderHandler::paginate()` (biblioteca interna `wpfluent`) lêem a página/per_page de
|
|
`$_GET['page']`/`$_REQUEST['per_page']` — pensado para pedidos HTTP do admin, inexistentes num
|
|
`wp eval` de CLI. Sem preencher esses dois superglobais antes de chamar `Logger::get()`, a
|
|
paginação colapsa sempre para `page=1`/`per_page=15` **ignorando silenciosamente** qualquer
|
|
`page`/`per_page` passado nos dados — reproduzido ao vivo antes de corrigir (`fluentsmtp_list_email_logs`
|
|
com `page:2` devolvia sempre a página 1 até `$_GET['page']`/`$_REQUEST['per_page']` serem
|
|
preenchidos explicitamente no corpo PHP, isolado a este processo efémero). Os filtros
|
|
`where()`/`whereBetween()` (status/pesquisa/intervalo de datas) não sofrem deste problema —
|
|
sobrevivem à chamada de `paginate()` porque ficam no query builder antes dela ser invocada.
|
|
|
|
## Tools (6)
|
|
|
|
| Tool | Read-only | Uso |
|
|
|---|---|---|
|
|
| `fluentsmtp_list_sites` | sim | Aliases conhecidos, path WP, prefixo de tabela, nota de estado |
|
|
| `fluentsmtp_get_status` | sim | Versão, nº connections, default/fallback (com verificação de existência — fallback pode ficar órfão), definições de log/retenção. Nunca devolve segredos |
|
|
| `fluentsmtp_list_connections` | sim | Todas as connections — provider, remetente, `secret_configured: true/false` (NUNCA o valor, mesmo cifrado), qual é default/fallback, e-mails mapeados |
|
|
| `fluentsmtp_list_email_logs` | sim | Filtros `status`/`search`/`date_from`+`date_to`, paginação real (`page`/`per_page`) |
|
|
| `fluentsmtp_get_email_log` | sim | Detalhe completo de 1 log por `id` — corpo truncado a 5000 caracteres, cabeçalhos, resposta do provider, histórico de reenvios em `extra` |
|
|
| `fluentsmtp_get_stats` | sim | Totais vitalícios (`sent`/`failed`), totais do período por status, série diária/semanal/mensal do volume total |
|
|
| `fluentsmtp_resend_email` | **não** | Reenvia de verdade 1 email via `Logger::resendEmailFromLog()`. **Exige `confirm:true`.** Aceita `recipients` para redireccionar o envio para endereço(s) controlados em vez do(s) destinatário(s) original(is) |
|
|
|
|
Todas as tools exigem `site` como primeiro parâmetro.
|
|
|
|
## Decisão deliberada: sem escrita de configuração de connections
|
|
|
|
Nenhuma tool escreve `fluentmail-settings` (criar/editar/apagar connection, mudar
|
|
default/fallback, activar/desactivar simulação). Razão: um erro nessa escrita — chave errada,
|
|
provider mal configurado, `default_connection` a apontar para um ID inexistente (já aconteceu
|
|
organicamente em produção, ver `fallback_connection` órfão em `descomplicar`) — parte o envio de
|
|
email do **site inteiro** (resets de password, notificações, formulários, FluentCRM), sem forma
|
|
fácil de detectar isso a partir de fora até um utilizador reportar "não recebi o email". O
|
|
risco/benefício não compensa para um v1. Se for necessário no futuro: replicar o padrão
|
|
guarda-corpo + `confirm:true` + leitura-antes-de-escrever de `mcp-fluent-crm`
|
|
(`fcrm_update_contact_status`), nunca um `update_option` cego.
|
|
|
|
## Segurança
|
|
|
|
- Segredos de connection **nunca** devolvidos em nenhuma tool, mesmo cifrados — só o booleano
|
|
`secret_configured` por connection (`fluentsmtp_list_connections`).
|
|
- `fluentsmtp_resend_email` exige `confirm:true` (zod `z.literal(true)`, rejeitado no schema antes
|
|
de chegar ao handler — testado ao vivo com `confirm:false`, erro `-32602` antes de qualquer SSH).
|
|
- `recipients` em `fluentsmtp_resend_email` permite testar/reenviar sem depender do(s)
|
|
destinatário(s) original(is) — usado nos testes deste MCP para nunca enviar a um terceiro
|
|
externo sem necessidade.
|
|
- Corpo do log truncado a 5000 caracteres em `fluentsmtp_get_email_log` — não é segredo, mas evita
|
|
devolver megabytes de HTML/anexos codificados.
|
|
|
|
## Verificação
|
|
|
|
Construído e testado ponta-a-ponta contra os 3 sites reais em 19-08-2026, via processo stdio real
|
|
(não só chamadas SSH soltas):
|
|
|
|
- `fluentsmtp_get_status` em `descomplicar`: 2 connections, default=SMTP directo,
|
|
`fallback_connection_exists: false` (órfão, confirmado por leitura directa da option);
|
|
em `starter`: 1 connection, `secrets_encrypted_at_rest: false` (confirmado, `use_encrypt` ausente
|
|
nesse site); em `emanuelalmeida`: 1 connection, fallback presente mas vazio.
|
|
- `fluentsmtp_list_connections` em `descomplicar` e `emanuelalmeida`: segredos correctamente
|
|
mascarados (`secret_configured: false` no Elastic Email sem API key, `true` no SMTP directo com
|
|
password cifrada preenchida) — nunca o valor devolvido.
|
|
- `fluentsmtp_list_email_logs` em `descomplicar` com `status:failed`, `page:2`, `per_page:3`:
|
|
`total: 19` (confirmado por `SELECT COUNT(*) ... WHERE status='failed'` directo), ids
|
|
correctos para a página 2 depois da correcção do gotcha de paginação.
|
|
- `fluentsmtp_get_email_log` para o log `#50`: destinatário, assunto e erro do provider
|
|
(`"SMTP Error: Could not authenticate."`) confirmados coincidentes com a linha da tabela.
|
|
- `fluentsmtp_get_stats` em `descomplicar` (01-19 ago 2026): `lifetime.sent=58`,
|
|
`lifetime.failed=19` — confirmado por `SELECT COUNT(*) GROUP BY status` directo; em
|
|
`emanuelalmeida`: `lifetime.sent=20`, `lifetime.failed=0`.
|
|
- `fluentsmtp_resend_email` (escrita real, única): `confirm:false` rejeitado no schema antes de
|
|
qualquer SSH (`-32602 Invalid literal value, expected true`). `confirm:true` no log `#74`
|
|
(`descomplicar`, falhado em 2026-08-14) com `recipients:["it@descomplicar.pt"]` — redireccionado
|
|
do destinatário original para este endereço interno da própria empresa, nunca a um terceiro
|
|
externo. Resultado: `status: sent`, `resent_count: 1`, confirmado por leitura directa da linha
|
|
(`extra` com o registo do reenvio: `to=it@descomplicar.pt`, `sent=true`, `ms=207.2`).
|
|
|
|
## Skills relacionadas
|
|
|
|
- `mcp-fluent-crm` — mesmo mecanismo `wp eval` sobre camada de serviço oficial, para contactos e
|
|
campanhas em descomplicar.pt; FluentSMTP é o transporte que o FluentCRM usa para enviar.
|
|
- `mcp-wpfc` / `mcp-wpmeteor` — outros MCPs multi-site do mesmo bundle, padrão `site` como
|
|
parâmetro em cada tool.
|