ESET Protect MCP
Servidor MCP para a API ESET Connect - 102 ferramentas, modo RO/RW, stdio+HTTP, OAuth2
Documentação
ESET-MCP
Um servidor Model Context Protocol para toda a superfície de gerenciamento ESET: ESET Connect (nuvem, todas as regiões), ESET PROTECT On-Prem, ESET Inspect e ESET Cloud Office. Acione qualquer um deles a partir de qualquer host MCP (Claude Desktop, Claude Code, ou um agente personalizado) por meio de ferramentas, recursos e prompts.
Construído como um hub único para qualquer número de implantações ESET. Um único processo atende consoles de nuvem e on-prem ao mesmo tempo; os clientes escolhem o destino por requisição via cabeçalhos. Desde que o MCP receba credenciais válidas (autenticação Basic, além de um override opcional de URL e um token de serviço opcional do Cloudflare Access), ele roteia a chamada para o backend correto, gera seus próprios tokens e mantém os tenants isolados no pool.
⚠️ Só para deixar claro, pessoal
Este é um projeto open-source independente, conduzido pela comunidade, e não é afiliado, oficialmente suportado ou endossado pela ESET, spol. s r.o. ESET e seus nomes de produtos são marcas registradas de seus respectivos proprietários.Embora todos os esforços tenham sido feitos para garantir que este software seja seguro e robusto (incluindo o gate estrito de modo Read-Only), este código é fornecido "COMO ESTÁ", sem qualquer garantia de qualquer tipo. Você é o único responsável por como usa esta ferramenta e por quaisquer alterações feitas no seu ambiente ESET.
Sumário
- Recursos
- Arquitetura em resumo
- Segurança
- Início rápido
- Configuração
- Implantação multi-tenant (modo basic-auth)
- Suporte a ESET PROTECT On-Prem
- Implantação em produção (HTTPS via Caddy)
- Ferramentas, recursos e prompts
- Arquitetura
- Testes
- Atualizando as especificações OpenAPI
- Licença
Recursos
Cobertura completa da API
- 102 ferramentas geradas automaticamente a partir de 16 especificações oficiais ESET Connect OpenAPI 3.0.1, cobrindo gerenciamento de aplicações, gerenciamento de ativos, automação, gerenciamento de dispositivos, identidade, gerenciamento de incidentes, gerenciamento de instaladores, gerenciamento de dispositivos móveis, proteção de acesso à rede, gerenciamento de patches, gerenciamento de políticas, gerenciamento de quarentena, gerenciamento de usuários, gerenciamento de vulnerabilidades e proteção de acesso à web.
- 4 composites de alto nível que agrupam 3-6 chamadas brutas em uma:
eset_search,device_full_profile,incident_full_context,latest_detections.
Modos somente leitura / leitura-escrita
ESET_MODE=RO→ o catálogo expõe apenas ferramentas somente leitura (51 no total). Ferramentas de escrita ficam ocultas dolist_toolscompletamente.ESET_MODE=RW→ todas as 106 ferramentas são anunciadas; ferramentas de mutação carregamdestructiveHint: trueem suas anotações MCP.- Independente das permissões subjacentes da conta ESET.
- Um gate defensivo em memória (defence-in-depth) rejeita nomes de ferramentas RW no modo RO antes que qualquer requisição HTTP seja enviada.
Autenticação
ESET_AUTH_MODE=env- tenant único, credenciais de.env.ESET_AUTH_MODE=basic- multi-tenant, clientes passamAuthorization: Basic <base64(user:password)>por requisição (além de opcionalX-ESET-Regionpara uma região de nuvem diferente, ouX-ESET-Server-URLpara rotear a requisição para um console PROTECT on-prem). Um servidor atende muitas contas ESET e pode misturar nuvem + on-prem no mesmo processo.- Tokens OAuth por tenant, agrupados e isolados por
(user, password_hash, deployment, region-or-server-url, cf_secret_hash). Rotacionar uma senha ou token de serviço do Cloudflare Access gera um novo cliente; clientes de nuvem e on-prem para o mesmo usuário nunca compartilham uma entrada do pool.
Transportes
- stdio - JSON-RPC sobre stdin/stdout para hosts locais.
- Streamable HTTP - o transporte MCP atual (especificação de novembro de 2025).
Multi-região
eu / de / us / ca / jpn. Fixado via ESET_REGION no modo env;
por requisição via X-ESET-Region no modo basic.
Nuvem + on-prem em um único processo
Além das regiões de nuvem, um único servidor MCP pode atender
consoles ESET PROTECT On-Prem hospedados pelo cliente. O formato de autenticação on-prem
(POST /GetTokens com resposta camelCase) e a estrutura de URL por host
são tratados de forma transparente; os clientes escolhem o destino por requisição
via cabeçalho X-ESET-Server-URL. Veja
Suporte a ESET PROTECT On-Prem.
Cloudflare Access (opcional)
Quando o console on-prem está atrás de um túnel Cloudflare Access, o MCP
autentica como um service token
(default de env ou por requisição X-ESET-CF-Access-Client-Id /
X-ESET-CF-Access-Client-Secret) e atravessa até a origem. O
token CF é uma camada extra de ingresso na frente - não um substituto para -
as credenciais da conta ESET. Requisições de nuvem nunca carregam esses cabeçalhos.
Resiliência
- OAuth2 com refresh proativo ~5 minutos antes da expiração do token e um refresh forçado
- retry em 401.
- Retries de 429 com backoff exponencial (até 3 tentativas, respeita
Retry-After). - Paginação (
nextPageToken) percorrida de forma transparente. - Long-polling de 202 com o cabeçalho
response-id, até 10 minutos.
Modelagem de resposta (proteção da janela de contexto)
Uma única chamada list_* sem limite pode retornar centenas de KB - o suficiente para
estourar o contexto de um modelo. Duas transformações são aplicadas a cada
resposta de ferramenta:
- Projeção
fields- toda ferramenta GET expõe um parâmetro opcionalfields: [string]que filtra cada item da lista para as chaves solicitadas (ex.:["uuid", "displayName"]). Aplicado no lado do servidor após a busca. - Limite de bytes (
ESET_MCP_RESPONSE_BYTES_MAX, padrão 100 KB) - se um payload ainda exceder o orçamento, a lista mais longa é truncada enquanto todo campo de nível superior (nextPageToken,totalSize, …) é preservado, e um bloco de metadados_cappedé anexado com uma dica acionável sobre como continuar. Os agentes mantêm acesso total aos dados através da paginação.
Erros amigáveis para agentes
Erros HTTP são mapeados para dicas legíveis: 403 → verifique Permission Sets no ESET PROTECT Hub; 401 → o servidor atualiza o token automaticamente; 429 → recue; 5xx → tente novamente em breve.
Observabilidade (logs + Prometheus)
- Logs estruturados para stderr. Texto (padrão) para dev, JSON Lines para
shippers de log de produção via
ESET_MCP_LOG_FORMAT=json. Cada chamada de ferramenta, refresh de token, retry HTTP e evicção de pool emite um registroeventtipado com campos de baixa cardinalidade (tool, deployment, status, duration_ms, response_bytes, ...). - Métricas Prometheus em um endpoint
/metricsopt-in (ESET_MCP_METRICS_ENABLED=true, requerpip install eset-mcp[metrics]). Contadores para chamadas de ferramentas, refreshes de token, retries HTTP, hits de limite; histogramas para duração de ferramentas e tamanhos de resposta; gauge para tamanho do pool de clientes. - O que nunca entra em logs ou métricas: senhas, cabeçalhos
Authorization, segredos do CF Access, corpos de requisição/resposta, query strings, parâmetros de caminho substituídos (que podem vazar UUIDs). Uma lista de negação defensiva no logger remove chaves conhecidamente sensíveis antes que qualquer formatador as veja. - Isolamento de falhas: a emissão de telemetria é envolvida em try/except para que
um registry ou formatador de métricas quebrado nunca transforme uma chamada de
ferramenta bem-sucedida em um erro para o agente.
/metricsretorna 500 (não 503) se a exposição falhar - o worker permanece ativo. - Silenciamento em produção: defina
ESET_LOG_LEVEL=WARNINGpara silenciar os eventos INFO por chamada, mas manter retries/erros visíveis; definaESET_LOG_LEVEL=ERRORpara silenciar tudo exceto falhas graves. Desative métricas completamente comESET_MCP_METRICS_ENABLED=false(padrão). Os três controles são independentes.
Arquitetura em resumo
A configuração mais simples: credenciais em .env, um host MCP, uma região de nuvem ESET.
Ideal para uso pessoal, um único time, ou um cliente de IA de desktop como
Claude Desktop ou Claude Code.
Para implantações reais multi-tenant ou empresariais, coloque um gerenciador de credenciais na frente do ESET-MCP. O diagrama abaixo mostra um padrão usando IBM mcp-context-forge como a camada de "gerenciamento de credenciais" - qualquer gateway MCP equivalente (ou seu próprio proxy de autenticação) funciona da mesma forma:
O forge guarda segredos por tenant e injeta os cabeçalhos Authorization: Basic,
X-ESET-Region, X-ESET-Server-URL e X-ESET-CF-Access-* por
requisição. O ESET-MCP roteia cada requisição para o backend correto (região de nuvem,
console PROTECT on-prem, ou on-prem atrás de Cloudflare Access) e o
pool LRU de clientes por tenant mantém os tokens OAuth totalmente isolados entre
tenants. Este é um padrão funcional, não aspiracional - os cabeçalhos, chaves
de pool e regras de roteamento descritos aqui estão todos na suíte de testes em
tests/test_concurrency.py e tests/test_onprem.py.
Segurança
Credenciais
- No modo
enva senha é lida uma vez na inicialização e mantida em memória. - No modo
basica senha está em trânsito apenas durante a duração da requisição, e em memória apenas enquanto o cliente por tenant está ativo no pool LRU. Ela nunca é registrada em log. - A chave do pool usa um hash SHA-256 da senha em vez da própria senha.
- Tokens de acesso/refresh OAuth são mantidos por tenant; tokens nunca cruzam limites de tenant dentro de uma única sessão.
Modos de autenticação e transporte
| Modo | Transporte permitido | Fonte de credenciais |
|---|---|---|
env | stdio ou http | .env (ESET_USER / ESET_PASSWORD) |
basic | apenas http | cabeçalho Authorization: Basic por requisição |
O modo basic sobre HTTP simples vazaria senhas. O servidor impõe
transporte HTTP para o modo basic na inicialização, mas não impõe TLS -
isso é responsabilidade da implantação. O perfil prod do docker-compose coloca
o servidor atrás de Caddy + Let's Encrypt.
Authorization ausente ou malformado no modo basic → HTTP 401 com um
desafio WWW-Authenticate: Basic. Região desconhecida em X-ESET-Region
→ HTTP 401.
Isolamento RO / RW
Duas camadas independentes:
- Ocultação do catálogo -
list_toolsfiltra toda ferramenta não-GET no modo RO. O agente nunca vê ferramentas de escrita. - Gate defence-in-depth -
call_toolvalida o modo declarado da ferramenta contraESET_MODEantes que qualquer requisição HTTP saia. Clientes hard-coded, tentativas de prompt-injection e snapshots de agente desatualizados atingem o gate e recebem uma resposta de textoModeForbiddenErrorestruturada (sem exceção, sem chamada de rede).
No modo RW, ferramentas de mutação carregam destructiveHint: true para que hosts MCP
que respeitam anotações possam exigir confirmação por chamada.
Isolamento por tenant (modo basic-auth)
- Cabeçalhos de autenticação são parseados em middleware ASGI dedicado e guardados em um
ContextVar; eles nunca entram em corpos de requisição ou logs. - Cada requisição resolve para uma instância
Credentialschaveada por(user, password_hash, region). - Um limite LRU no pool de clientes previne crescimento ilimitado de memória por spray de credenciais aleatórias.
Superfície de rede
- Em dev (
docker compose up) o servidor MCP publica:8765. - No perfil
prodo container MCP não tem porta publicada - o Caddy entra na mesma rede bridge docker e faz proxy de HTTPS. As únicas portas do host são 80 (HTTP-01 ACME) e 443 (HTTPS). - Sem tráfego de saída exceto para
*.eset.systems(auth + APIs).
Auditoria de dependências e código
- Snyk Code: 0 problemas em
eset_mcp/. - Ruff: limpo (
select = E F W I B UP RUF). - Dependências de runtime:
mcp,httpx,pydantic,python-dotenv. Além destarlette+uvicornao executar HTTP.
Fora do escopo (por design)
- Sem receivers de webhook.
- Sem armazenamento persistente; logs vão para stdout.
- Sem cache em disco de tokens OAuth.
- Sem write-back de credenciais do modo
basicpara disco.
Divulgação responsável
Por favor, abra um advisory de segurança privado em vez de um issue público: https://github.com/maciekaz/ESET-MCP/security/advisories/new.
Início rápido
O caminho mais rápido é a imagem Docker publicada. Sem instalação de Python, sem
venv, sem checkout do código-fonte — apenas .env + docker run. A imagem é
multi-arquitetura (amd64 + arm64), assinada com cosign, acompanha SBOM +
proveniência de build e é publicada no GHCR a cada release.
cp .env.example .env # fill in ESET_USER / ESET_PASSWORD / ESET_REGION
docker run --rm -i --env-file .env ghcr.io/maciekaz/eset-mcp:1
Política de fixação de versão:
:1- última 1.x.x (atualiza automaticamente dentro da major):1.0- última 1.0.x (atualiza automaticamente dentro da minor):1.0.1- versão exata (produção):latest- release estável mais recente:main/:sha-<short>- builds edge da main (não para produção)
Conectar ao Claude Desktop / Claude Code (stdio)
// claude_desktop_config.json
{
"mcpServers": {
"eset": {
"command": "docker",
"args": ["run", "--rm", "-i", "--env-file", "/absolute/path/to/.env",
"ghcr.io/maciekaz/eset-mcp:1"]
}
}
}
Transporte HTTP (single-tenant ou atrás do seu próprio proxy)
docker run -d --name eset-mcp \
--env-file .env -p 8765:8765 \
-e ESET_MCP_TRANSPORT=http \
ghcr.io/maciekaz/eset-mcp:1
# MCP endpoint: http://localhost:8765/mcp
Verificar a imagem (cosign keyless)
cosign verify \
--certificate-identity-regexp '^https://github.com/maciekaz/ESET-MCP/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/maciekaz/eset-mcp:1
A partir do código-fonte (contribuidores / desenvolvimento local)
git clone https://github.com/maciekaz/ESET-MCP.git
cd ESET-MCP
cp .env.example .env
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
eset-mcp
Docker Compose (usa a imagem publicada)
docker compose up -d eset-mcp-http
# MCP endpoint: http://localhost:8765/mcp
Por padrão, o arquivo compose baixa ghcr.io/maciekaz/eset-mcp:1 — sem
build local, primeira inicialização rápida. Fixe uma versão específica editando a
linha image: em docker-compose.yml.
stdio avulso via compose:
docker compose --profile stdio run --rm eset-mcp-stdio
Mexendo no código-fonte? Use o perfil dev para compilar a partir do seu checkout local em vez de baixar:
docker compose --profile dev up --build eset-mcp-http-dev
Configuração
Todas as configurações ficam em .env. Os campos obrigatórios estão
marcados em .env.example.
| Variável | Padrão | Finalidade |
|---|---|---|
ESET_AUTH_MODE | env | env (single tenant) ou basic (multi tenant) |
ESET_USER | - | Usuário da API (obrigatório no modo env) |
ESET_PASSWORD | - | Senha da API (obrigatória no modo env) |
ESET_MODE | RO | RO (catálogo somente leitura) ou RW |
ESET_REGION | eu | eu / de / us / ca / jpn |
ESET_MCP_TRANSPORT | stdio | stdio ou http |
ESET_MCP_HTTP_HOST | 127.0.0.1 | Endereço de bind HTTP |
ESET_MCP_HTTP_PORT | 8765 | Porta HTTP |
ESET_MCP_RESPONSE_BYTES_MAX | 100000 | Limite de bytes por chamada; 0 desativa |
ESET_LOG_LEVEL | INFO | DEBUG / INFO / WARNING / ERROR |
ESET_MCP_LOG_FORMAT | text | text (dev, legível para humanos) ou json (shippers de log em produção) |
ESET_MCP_METRICS_ENABLED | false | Montar /metrics do Prometheus; requer eset-mcp[metrics] |
ESET_MCP_METRICS_PATH | /metrics | Onde montar o endpoint de métricas |
ESET_DEPLOYMENT | cloud | cloud (ESET Connect) ou onprem (PROTECT hospedado pelo cliente) |
ESET_ONPREM_SERVER_URL | - | https://host[:port] do console on-prem (obrigatório em env+onprem) |
ESET_ONPREM_VERIFY_SSL | true | Defina false para consoles on-prem com certificados autoassinados |
ESET_ONPREM_CF_ACCESS_CLIENT_ID | - | client-id do Cloudflare Access Service Token (on-prem atrás do CF) |
ESET_ONPREM_CF_ACCESS_CLIENT_SECRET | - | client-secret do Cloudflare Access Service Token (pareado com o acima) |
ESET_PUBLIC_DOMAIN | - | Domínio para o qual o Caddy emite um certificado TLS (apenas perfil prod) |
ESET_ACME_EMAIL | - | E-mail que o Let's Encrypt usa para renovações (apenas perfil prod) |
Use um usuário de API dedicado — não o seu login do console. Crie um em ESET PROTECT Hub / ESET Business Account → API users.
Implantação multi-tenant (modo basic-auth)
# .env
ESET_AUTH_MODE=basic
ESET_MCP_TRANSPORT=http
ESET_REGION=eu # default region; clients can override per request
Toda requisição HTTP deve conter:
| Cabeçalho | Obrigatório | Observações |
|---|---|---|
Authorization | sim | Basic <base64(user:password)> |
X-ESET-Region | não | Substitui a região padrão (eu/de/us/ca/jpn) |
X-ESET-Server-URL | não | Roteia esta requisição para um console PROTECT on-prem (ex.: https://protect.example.com:9443) — veja Suporte on-prem |
X-ESET-CF-Access-Client-Id | não | client-id do Cloudflare Access Service Token (on-prem atrás do CF Access) |
X-ESET-CF-Access-Client-Secret | não | Pareado com o acima — ambos devem ser enviados juntos |
Exemplo de cliente Python:
import base64
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
token = base64.b64encode(b"api-user@tenant.tld:secret").decode()
headers = {"Authorization": f"Basic {token}", "X-ESET-Region": "us"}
async with streamablehttp_client(
"https://eset-mcp.example.com/mcp/", headers=headers
) as (r, w, _):
async with ClientSession(r, w) as session:
await session.initialize()
tools = await session.list_tools()
⚠️ Basic auth sem TLS vaza credenciais. Sempre execute o modo
basicatrás de HTTPS.
Suporte a ESET PROTECT on-prem
A ESET distribui o PROTECT tanto como serviço em nuvem (a API ESET Connect em
*.eset.systems) quanto como console on-prem que os clientes hospedam. A API
REST on-prem fica em um único host (porta padrão 9443) e usa um endpoint de
autenticação diferente — POST /GetTokens com corpo JSON e resposta em
camelCase — mas, fora isso, compartilha a estrutura de URL da API em nuvem.
O ESET-MCP suporta ambos, no mesmo processo.
Como o servidor decide entre nuvem e on-prem
ESET_AUTH_MODE | O que controla a implantação por requisição |
|---|---|
env | Estático: ESET_DEPLOYMENT (nuvem) ou ESET_DEPLOYMENT=onprem + ESET_ONPREM_SERVER_URL |
basic | Por requisição: a presença de X-ESET-Server-URL alterna aquela requisição específica para on-prem; a ausência volta ao padrão do ambiente (nuvem ou on-prem) |
Assim, um único servidor MCP pode atender a nuvem para a maioria dos clientes e
rotear requisições específicas para um ou mais consoles on-prem — determinado
inteiramente pela URL que cada cliente envia em X-ESET-Server-URL.
On-prem single-tenant (modo env)
# .env
ESET_AUTH_MODE=env
ESET_DEPLOYMENT=onprem
ESET_ONPREM_SERVER_URL=https://protect.company.local:9443
ESET_ONPREM_VERIFY_SSL=true # set to false only for self-signed certs you trust
ESET_USER=api-user@company.local
ESET_PASSWORD=...
On-prem multi-tenant (basic auth, URL por requisição)
# .env
ESET_AUTH_MODE=basic
ESET_MCP_TRANSPORT=http
ESET_DEPLOYMENT=cloud # default; clients opt into on-prem per-request
# ESET_ONPREM_SERVER_URL is optional - if set it becomes the on-prem default
Cliente direcionado ao on-prem:
headers = {
"Authorization": f"Basic {token}",
"X-ESET-Server-URL": "https://protect.client-a.local:9443",
}
Mesmo servidor MCP, requisição diferente — mesmos cabeçalhos menos X-ESET-Server-URL
— permanece na nuvem.
O que funciona no on-prem vs nuvem
O catálogo de ferramentas é idêntico para ambas as implantações (todas as 102 ferramentas derivadas do OpenAPI mais as 4 compostas). No momento da chamada, o servidor usa caminhos de nuvem para credenciais de nuvem e caminhos on-prem para credenciais on-prem.
- Compartilhadas e verificadas:
device_*,asset_groups_*,policy_*e a maior parte detask_*(Automação) funcionam igualmente na nuvem e no on-prem. - Módulos somente nuvem:
incident_*,mobile_*,wap_*,nap_*,quarantine_*e a maior parte devuln_*correspondem a produtos ESET separados (ESET Inspect, Cloud Office Security, MDM) que não fazem parte da instalação do PROTECT on-prem. Chamá-los contra um console on-prem retorna um 404 simples da ESET — apresentado ao agente como uma resposta de textoESET API error: 404sem tratamento especial. - Substituições de caminho: alguns endpoints têm URL diferente no on-prem — ex.:
POST /v1/devices/{uuid}:renameé:renameDeviceno on-prem. Eles são declarados emeset_mcp/openapi/onprem-path-overrides.jsone aplicados automaticamente quando a requisição tem como alvo o on-prem.
Cloudflare Access na frente do console on-prem
Quando o console PROTECT on-prem é exposto por um túnel Cloudflare e protegido por Cloudflare Access, o MCP pode autenticar como um service token. A cadeia se torna MCP → Cloudflare Access → ESET on-prem.
Dois valores por par de token, fornecidos via .env:
ESET_ONPREM_CF_ACCESS_CLIENT_ID=abc1234567890.access
ESET_ONPREM_CF_ACCESS_CLIENT_SECRET=<long-secret>
…ou por requisição no modo basic-auth (substitui os padrões do ambiente — útil quando cada tenant tem seu próprio túnel e seu próprio service token):
headers = {
"Authorization": f"Basic {token}",
"X-ESET-Server-URL": "https://protect.client-a.local:9443",
"X-ESET-CF-Access-Client-Id": "abc1234567890.access",
"X-ESET-CF-Access-Client-Secret": "<long-secret>",
}
O MCP traduz os cabeçalhos de entrada X-ESET-CF-* nos cabeçalhos reais
CF-Access-Client-Id / CF-Access-Client-Secret que o Cloudflare
Access espera e os anexa a todas as chamadas de saída — tanto o
handshake de autenticação POST /GetTokens quanto todas as requisições
subsequentes à API da ESET.
O segredo do CF é tratado como a senha: nunca é registrado em log, apenas seu hash SHA-256 entra na chave do pool de clientes. Rotacionar o segredo gera um novo cliente
- novo token ESET. Requisições de nuvem nunca carregam cabeçalhos CF Access, independentemente dos padrões do ambiente — o ESET Connect é um SaaS público.
Notas de segurança para on-prem
X-ESET-Server-URLaceita apenas URLshttps://sem caminho, query ou fragmento. Barras finais são removidas. Qualquer outra coisa → HTTP 400.ESET_ONPREM_VERIFY_SSL=falsedesativa a verificação de certificado TLS e expõe a conexão a MITM. O servidor registra um único WARNING por construção de cliente quando está desativado. Use apenas em intranets confiáveis com certificados autoassinados que você não pode substituir.- Tokens on-prem são mantidos em memória por
(user, password_hash, server_url, cf_secret_hash)— mesmas regras de isolamento dos tokens de nuvem. O pool os chaveia separadamente para que clientes de nuvem e on-prem nunca colidam, e dois clientes acessando a mesma URL on-prem com service tokens CF diferentes recebem entradas separadas no pool. - Enviar apenas um dos dois cabeçalhos
X-ESET-CF-*retorna HTTP 400 em vez de silenciosamente voltar ao padrão do ambiente (quase certamente um erro de digitação do operador).
Implantação em produção (HTTPS via Caddy)
O perfil docker-compose prod inicia o Caddy na frente do servidor MCP.
O Caddy obtém um certificado Let's Encrypt na primeira inicialização (desafio
HTTP-01 — as portas 80 / 443 devem estar acessíveis pela internet pública)
e faz proxy de HTTPS para o contêiner MCP interno.
# .env
ESET_AUTH_MODE=basic
ESET_PUBLIC_DOMAIN=eset-mcp.example.com
ESET_ACME_EMAIL=ops@example.com
docker compose --profile prod up -d
# MCP endpoint: https://eset-mcp.example.com/mcp
Você obtém:
- HTTPS na 443 com certificado Let's Encrypt de renovação automática.
- Desafio HTTP-01 na 80.
- Contêiner MCP vinculado apenas à rede bridge do Docker — sem porta publicada.
- Compressão gzip / zstd, logs de acesso JSON no stdout.
Ferramentas, recursos e prompts
Ferramentas compostas de alto nível
| Ferramenta | Retorna |
|---|---|
eset_search(query, kinds?, limit_per_kind?) | Correspondências de substring sem diferenciar maiúsculas/minúsculas em dispositivos / usuários / políticas / grupos |
device_full_profile(deviceUuid) | Registro do dispositivo + detecções recentes + vulnerabilidades + varreduras recentes |
incident_full_context(incidentUuid) | Incidente + comentários + detecções relacionadas + dispositivos afetados |
latest_detections(hours=24, limit=10, severity_min?) | Detecções mais recentes em uma janela de tempo, ordenadas por occurTime desc; fallback v2 → v1 |
Cada composta degrada graciosamente quando uma subchamada retorna 403/404
(ex.: em tenants sem um módulo). A forma carrega flags skipped /
truncated quando aplicável.
Recursos
eset://config/mode-ROouRW.eset://config/region- região atual (por solicitação no modo basic-auth).eset://config/deployment-cloudouonprem (<server-url>)para esta solicitação.eset://config/tools-catalog- catálogo JSON de todas as 106 ferramentas (nome, modo, método, caminho, serviço, descrição).eset://docs/rate-limits- lembrete rápido sobre o limite de 10 req/s.
Prompts
audit_inactive_devices(days=30)- candidatos a desligamento.vulnerability_report- relatório de CVE por dispositivo.incident_triage- incidentes abertos + detecções relacionadas.
Arquitetura
eset_mcp/
├── __main__.py # entrypoint - stdio or HTTP, wires resolver + pool
├── server.py # MCP server (tools / resources / prompts) + telemetry
├── credentials.py # Credentials + EnvResolver / BasicAuthResolver + ContextVar
├── middleware.py # ASGI Basic-auth middleware (basic mode only)
├── client_pool.py # LRU pool of EsetHttpClient keyed by (user, region, ...)
├── http_client.py # async httpx + 202 polling + 429 retry + 401 refresh
├── auth.py # CloudTokenManager (OAuth2) + OnPremTokenManager (/GetTokens)
├── regions.py # cloud region → per-service domains + on-prem URL resolver
├── modes.py # RO/RW gate
├── errors.py # HTTP error → agent-friendly text
├── config.py # .env loading
├── response_shaping.py # fields projection + byte cap
├── composite_tools.py # hand-written high-level tools
├── tools_loader.py # generator: tools from OpenAPI specs + on-prem path overrides
├── observability/ # JSON/text structured logging + Prometheus metrics
└── openapi/ # 16 ESET Connect OpenAPI 3.0.1 specs + onprem path overrides
Testes
pytest # full suite (RO smoke + unit + integration)
pytest -m "not rw" # RO only (default in CI)
pytest -m rw # RW (requires an account with RW permissions)
Os testes de integração atingem um tenant real da ESET - credenciais fornecidas via o mesmo .env. Fluxo de trabalho de CI: .github/workflows/integration.yml executa em PR, em push para main, e uma vez por dia às 03:17 UTC. O cron detecta divergências entre o servidor e as especificações OpenAPI publicadas pela ESET.
Atualizando as especificações OpenAPI
cd eset_mcp/openapi
for name in business-account application-management asset-management automation \
device-management iam incident-management installer-management \
mobile-device-management network-access-protection patch-management \
policy-management quarantine-management user-management \
vulnerability-management web-access-protection; do
curl -sO "https://eu.esetconnect.eset.systems/swagger/api/${name}.json"
done
tests/test_catalog_vs_openapi.py sinaliza quaisquer operações novas ou alteradas após uma atualização.
Licença
MIT