ESET Protect MCP

Servidor MCP para a API ESET Connect - 102 ferramentas, modo RO/RW, stdio+HTTP, OAuth2

Documentação

ESET-MCP

Tests License: MIT MCP spec

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

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 do list_tools completamente.
  • ESET_MODE=RW → todas as 106 ferramentas são anunciadas; ferramentas de mutação carregam destructiveHint: true em 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 passam Authorization: Basic <base64(user:password)> por requisição (além de opcional X-ESET-Region para uma região de nuvem diferente, ou X-ESET-Server-URL para 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 opcional fields: [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 registro event tipado com campos de baixa cardinalidade (tool, deployment, status, duration_ms, response_bytes, ...).
  • Métricas Prometheus em um endpoint /metrics opt-in (ESET_MCP_METRICS_ENABLED=true, requer pip 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. /metrics retorna 500 (não 503) se a exposição falhar - o worker permanece ativo.
  • Silenciamento em produção: defina ESET_LOG_LEVEL=WARNING para silenciar os eventos INFO por chamada, mas manter retries/erros visíveis; defina ESET_LOG_LEVEL=ERROR para silenciar tudo exceto falhas graves. Desative métricas completamente com ESET_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.

Single-tenant: one ESET account, one deployment

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:

Multi-tenant: many clients, one MCP, many backends

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 env a senha é lida uma vez na inicialização e mantida em memória.
  • No modo basic a 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

ModoTransporte permitidoFonte de credenciais
envstdio ou http.env (ESET_USER / ESET_PASSWORD)
basicapenas httpcabeç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:

  1. Ocultação do catálogo - list_tools filtra toda ferramenta não-GET no modo RO. O agente nunca vê ferramentas de escrita.
  2. Gate defence-in-depth - call_tool valida o modo declarado da ferramenta contra ESET_MODE antes 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 texto ModeForbiddenError estruturada (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 Credentials chaveada 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 prod o 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 de starlette + uvicorn ao 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 basic para 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ávelPadrãoFinalidade
ESET_AUTH_MODEenvenv (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_MODERORO (catálogo somente leitura) ou RW
ESET_REGIONeueu / de / us / ca / jpn
ESET_MCP_TRANSPORTstdiostdio ou http
ESET_MCP_HTTP_HOST127.0.0.1Endereço de bind HTTP
ESET_MCP_HTTP_PORT8765Porta HTTP
ESET_MCP_RESPONSE_BYTES_MAX100000Limite de bytes por chamada; 0 desativa
ESET_LOG_LEVELINFODEBUG / INFO / WARNING / ERROR
ESET_MCP_LOG_FORMATtexttext (dev, legível para humanos) ou json (shippers de log em produção)
ESET_MCP_METRICS_ENABLEDfalseMontar /metrics do Prometheus; requer eset-mcp[metrics]
ESET_MCP_METRICS_PATH/metricsOnde montar o endpoint de métricas
ESET_DEPLOYMENTcloudcloud (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_SSLtrueDefina 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çalhoObrigatórioObservações
AuthorizationsimBasic <base64(user:password)>
X-ESET-RegionnãoSubstitui a região padrão (eu/de/us/ca/jpn)
X-ESET-Server-URLnãoRoteia esta requisição para um console PROTECT on-prem (ex.: https://protect.example.com:9443) — veja Suporte on-prem
X-ESET-CF-Access-Client-Idnãoclient-id do Cloudflare Access Service Token (on-prem atrás do CF Access)
X-ESET-CF-Access-Client-SecretnãoPareado 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 basic atrá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_MODEO que controla a implantação por requisição
envEstático: ESET_DEPLOYMENT (nuvem) ou ESET_DEPLOYMENT=onprem + ESET_ONPREM_SERVER_URL
basicPor 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 de task_* (Automação) funcionam igualmente na nuvem e no on-prem.
  • Módulos somente nuvem: incident_*, mobile_*, wap_*, nap_*, quarantine_* e a maior parte de vuln_* 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 texto ESET API error: 404 sem tratamento especial.
  • Substituições de caminho: alguns endpoints têm URL diferente no on-prem — ex.: POST /v1/devices/{uuid}:rename é :renameDevice no on-prem. Eles são declarados em eset_mcp/openapi/onprem-path-overrides.json e 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-URL aceita apenas URLs https:// sem caminho, query ou fragmento. Barras finais são removidas. Qualquer outra coisa → HTTP 400.
  • ESET_ONPREM_VERIFY_SSL=false desativa 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

FerramentaRetorna
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 - RO ou RW.
  • eset://config/region - região atual (por solicitação no modo basic-auth).
  • eset://config/deployment - cloud ou onprem (<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