Skip to main content
Glama
MLAN1O

equatorial-mcp

by MLAN1O

equatorial-mcp

Servidor MCP comunitário e não oficial para consultar e baixar faturas da Equatorial.

Projeto comunitário, não oficial e sem afiliação com a Equatorial Energia.

Licença MIT. Veja LICENSE.

Escopo do v0.1.0

  • Suporte somente a Goiás (EQUATORIAL_UF=GO). Outras UFs (por exemplo PA e MA) falham com UF_UNSUPPORTED antes de abrir o navegador.

  • Três tools orientadas à intenção: listar_ucs, listar_faturas e baixar_fatura.

  • O PDF da segunda via é exposto como Resource MCP (equatorial://faturas/{artifact_id}/pdf); capturas de diagnóstico usam equatorial://diagnosticos/{error_id}/screenshot.

  • Operação local e determinística, sem LLM, sem OCR em nuvem, sem mensageria e sem agendamento.

  • Uma instância representa uma única conta configurada. Não use como serviço multi-tenant.

  • Nenhuma operação de pagamento é executada. O servidor apenas lista unidades consumidoras, lista faturas e baixa a segunda via em PDF com extração local.

Requisitos

  • Node.js >=20.

  • Chromium compatível via Puppeteer (dependência fixada no lockfile).

Instalação

A partir do tarball publicado:

npm install -g equatorial-mcp
equatorial-mcp

Uso local sem instalar:

npx -y equatorial-mcp

A partir do repositório:

npm ci
npm run build
npm pack --dry-run

Configuração do servidor no cliente

Os arquivos em examples/ configuram o transporte MCP e não contêm segredos: o servidor herda o ambiente privado do processo. Exemplos:

  • Claude Code e Cursor usam o formato mcpServers — veja examples/mcp.json (command npx, args ["-y", "equatorial-mcp"], env vazio).

  • OpenCode v2 usa o aninhamento mcp.servers com type: "local" e comando em array — veja examples/opencode.jsonc.

Prefira o escopo de usuário/local do cliente (nunca commite credenciais):

  • Claude Code: registre o servidor no escopo de usuário e informe as cinco variáveis no armazenamento privado de ambiente do cliente ou no shell que o inicia.

  • Cursor: coloque a entrada de examples/mcp.json no seu mcp.json de usuário e defina as variáveis fora do repositório.

  • OpenCode: mescle a entrada de examples/opencode.jsonc sob mcp.servers no seu opencode.json de usuário ou do projeto, sem valores secretos.

Instalação da Skill

A Skill canônica é publicada em skills/equatorial-faturas/SKILL.md (dentro do repositório e do tarball NPM). A instalação é por cópia — nenhum postinstall escreve no seu home. Copie o diretório inteiro equatorial-faturas, para que futuras referências e anexos continuem junto do SKILL.md:

  • Claude Code (projeto): .claude/skills/equatorial-faturas/SKILL.md

  • Claude Code (usuário): ~/.claude/skills/equatorial-faturas/SKILL.md

  • OpenCode (projeto): .opencode/skills/equatorial-faturas/SKILL.md

  • OpenCode (usuário): ~/.config/opencode/skills/equatorial-faturas/SKILL.md

  • Cursor (projeto): .cursor/skills/equatorial-faturas/SKILL.md ou o local compartilhado documentado .agents/skills/equatorial-faturas/SKILL.md

  • Cursor (usuário): ~/.cursor/skills/equatorial-faturas/SKILL.md

Exemplo (projeto Claude Code, a partir da raiz do repositório):

mkdir -p .claude/skills
cp -r skills/equatorial-faturas .claude/skills/

Em atualizações, copie novamente por cima para manter a Skill sincronizada com o servidor.

Configuração

O binário não carrega .env automaticamente e não depende de dotenv: .env.example é apenas documentação. Exporte as variáveis no ambiente do processo ou na configuração privada do cliente MCP. Nunca cole CPF, nascimento, cookies ou códigos de pagamento em chamadas de tool.

export EQUATORIAL_UF=GO
export EQUATORIAL_UC='SUA_UC'
export EQUATORIAL_CPF='SEU_CPF'
export EQUATORIAL_NASCIMENTO='DD/MM/AAAA'
export HEADLESS='true'

Variáveis normativas:

Variável

Obrigatória

Padrão

EQUATORIAL_UF

não

GO

EQUATORIAL_UC

sim

EQUATORIAL_CPF

sim

EQUATORIAL_NASCIMENTO

sim

HEADLESS

não

true

Não há aliases: variáveis com outros nomes são ignoradas e segredos não têm flags CLI. EQUATORIAL_CPF exige 11 dígitos com dígitos verificadores válidos; EQUATORIAL_NASCIMENTO exige data real em DD/MM/AAAA; HEADLESS aceita somente true/false. EQUATORIAL_CPF aceita entrada formatada com pontuação (somente os dígitos são validados) e HEADLESS é insensível a maiúsculas/minúsculas com espaços aparados, com vazio/ausente assumindo true.

Transporte

  • Padrão: stdio (stdin/stdout exclusivos do protocolo, diagnósticos em stderr).

  • Opt-in local: --http serve POST /mcp e GET /healthz (responde {"status":"ok"}) somente em loopback. --host aceita apenas 127.0.0.1, ::1 ou localhost (padrão 127.0.0.1); --port define a porta (padrão 3000). --host e --port exigem --http, e binds não-loopback são recusados porque o v0.1.0 não tem autenticação HTTP multiusuário.

  • Flags com aparência de segredo são recusadas: segredos entram somente pelo ambiente do processo.

npx -y equatorial-mcp --http --port 3000

Tools

Tool

Intenção

Observações

listar_ucs

Lista as Unidades Consumidoras disponíveis na conta Equatorial configurada. Não baixa faturas.

Somente leitura; aceita refresh para reler o portal.

listar_faturas

Lista faturas disponíveis para uma UC autorizada e devolve fatura_id opaca para download inequívoco.

Somente leitura; sem uc usa EQUATORIAL_UC. Lista vazia é sucesso com array vazio.

baixar_fatura

Baixa uma segunda via em PDF e extrai dados locais. Use preferencialmente a fatura_id retornada por listar_faturas.

Cria arquivo local (readOnlyHint: false); idempotente — reutiliza artefato já validado, salvo com force_download: true. Também aceita o par uc + conta_mes quando a correspondência for única.

Chamadas do mesmo perfil são serializadas: faça chamadas em série e não baixe todas as UCs por padrão.

Resultado estruturado e Resources

Toda tool devolve o valor canônico em structuredContent no envelope { "ok", "data", "error" }. Em sucesso, content traz um resumo curto e baixar_fatura acrescenta um bloco resource_link. Em falha de domínio/portal, o resultado usa isError: true e repete o envelope seguro com code, message, retryable, evidence_uri e details (somente campos da allowlist).

Campos extraídos de baixar_fatura (uc, conta_mes, vencimento, total, consumo_kwh, codigo_pix, linha_digitavel, saldos_scee) usam null para “não extraído/não presente” — nunca zero ou string inventada. parse_status resume a extração:

  • complete: campos principais obtidos e códigos de pagamento validados (seção SCEE ausente é legítima e usa saldos_scee.status: not_present);

  • partial: há texto útil, mas algo está em missing_fields — informe os ausentes e ofereça o PDF, sem inferir valores;

  • unreadable: PDF válido sem texto aproveitável — entregue/vincule o PDF e explique a conferência manual.

Regras de leitura: a uc principal é a consumidora (nunca a de injeção/geração SCEE); codigo_pix confiável começa com 000201 com validadores aprovados; linha_digitavel confiável começa com 341, com comprimento e dígitos verificadores aceitos; saldos_scee nulo não é zero.

O PDF nunca entra inline em base64 no JSON/texto inicial: a referência canônica é arquivo.resource_uri (formato equatorial://faturas/{artifact_id}/pdf) mais o bloco resource_link. O resources/read entrega o binário somente quando o host solicita. arquivo.local_path é auxiliar e pode apontar para dentro de um container — trate resource_uri como referência canônica. PIX e linha digitável completos fazem parte do resultado estruturado, mas o resumo textual os omite; repita-os na conversa somente com pedido explícito do usuário.

Estado local

Raiz padrão em sistemas Unix: ~/.equatorial-mcp/ (no Windows, sob o diretório de dados local da aplicação). Conteúdo:

~/.equatorial-mcp/
├── profile-key                 # segredo local aleatório, 0600
├── profiles/<uf>/<profile-id>/ # userDataDir do Chromium (cookies/sessão isolados por perfil)
├── downloads/<uf>/             # PDFs da segunda via, 0600
├── diagnostics/                # PNGs de diagnóstico temporários, 0600
├── manifests/
│   ├── artifacts.json          # IDs opacos -> caminhos/metadados
│   └── invoices.json           # fatura_id -> fingerprint seguro
└── locks/                      # lock interprocesso do perfil

Diretórios usam permissão 0700 e arquivos sensíveis 0600 em POSIX; no Windows a permissão é aplicada como melhor esforço da plataforma (o chmod ali só alterna o atributo de leitura). O profile-id deriva de HMAC-SHA-256 sobre UF + UC de login + CPF normalizado — o CPF nunca aparece em caminhos ou manifestos. Não commite esse diretório.

PDFs ficam sob seu controle e não são apagados silenciosamente. Capturas de diagnóstico expiram: na inicialização, o servidor remove capturas com mais de sete dias e nunca toca nos PDFs nessa limpeza.

Remoção precisa de estado

A remoção nunca é recursiva ampla: pare o servidor, faça backup do que for manter e remova somente o que você identificou.

  1. Pare o servidor (SIGINT/SIGTERM fecha navegador e sessão sem corromper perfil ou manifesto).

  2. Inspecione localmente os manifestos (manifests/artifacts.json e manifests/invoices.json) para mapear IDs opacos a arquivos — os PDFs físicos chamam-se downloads/<uf>/Fatura_<uc>_<mes>.pdf com a UC exata mais o mês YYYY-MM ou o prefixo de 8 caracteres da fatura_id (sem CPF e sem códigos de pagamento), e os manifestos registam a UC em texto claro ao lado dos IDs opacos (sem CPF/nascimento/pagamento).

  3. Para limpar cookies/sessão de um perfil, remova somente o diretório profiles/<uf>/<profile-id> desejado.

  4. Para descartar documentos, remova somente os arquivos listados no manifesto que você escolheu, junto dos registros correspondentes. Edite artifacts.json/invoices.json somente com o servidor parado e mantendo JSON/schema válidos — edições malformadas quebram a resolução e exigem restaurar o backup da etapa 1.

  5. Nunca apague um caminho amplo de home ou raiz. PDFs não são limpos automaticamente; capturas com mais de sete dias, sim.

Erros

Código

Quando ocorre

Conduta

CONFIG_MISSING / CONFIG_INVALID

variável ausente ou malformada

informe somente o nome da variável, sem ecoar valores

UF_UNSUPPORTED

UF registrada mas não implementada (PA/MA no v0.1.0)

informe que só GO é suportado; falha antes de abrir o navegador

AUTH_FAILED

login rejeitado

revise credenciais no ambiente privado, sem repeti-las

SESSION_EXPIRED

relogin único não recuperou a sessão

reinicie a operação manualmente

CAPTCHA_REQUIRED

desafio humano detectado

rode com HEADLESS=false, resolva manualmente e repita; nunca contorne

WAF_BLOCKED

403/429 ou página de bloqueio

pare as tentativas e aguarde antes de tentar manualmente

NAVIGATION_TIMEOUT

navegação excedeu o limite após a repetição permitida

verifique rede/site

SITE_CHANGED

seletor conhecido ausente

use o screenshot privado e abra issue sem anexar dados pessoais

UC_NOT_FOUND

UC fora das opções visíveis

chame listar_ucs

NO_INVOICES

fluxo concluído sem links

sucesso com lista vazia em listar_faturas; erro apenas em download direto inexistente

INVOICE_NOT_FOUND

referência/mês não existe mais

atualize com listar_faturas

INVOICE_AMBIGUOUS

mais de uma linha para o mês

escolha pela fatura_id

PDF_TIMEOUT / PDF_INVALID

captura excedeu o tempo ou resposta não é PDF

repetição manual única, com screenshot de apoio

PDF_TOO_LARGE

corpo acima de 20 MiB

download não é exposto; reporte o limite

RESOURCE_NOT_FOUND

URI opaca inexistente/expirada

baixe novamente

LOCK_TIMEOUT

outra operação retém o perfil

aguarde a operação anterior

IO_ERROR

storage/permissão/espaço

revise volume, permissão e espaço

INTERNAL_ERROR

falha não classificada

use o error_id; stack só existe no stderr redigido

Repetições automáticas são limitadas a uma única repetição controlada por operação e nunca disparam em rajada; CAPTCHA, credencial inválida, WAF e estrutura alterada nunca entram em retry automático.

Container

A imagem OCI é construída a partir da imagem oficial do navegador fixada por versão e digest no Dockerfile (nunca latest), em build multi-stage que compila sem segredos e publica somente dependências de produção, package.json, dist, Skill, exemplos, README, licença e exemplo de ambiente. O processo roda como usuário não-root da imagem, com stdio como transporte padrão e sandbox do navegador preservado.

docker build -t equatorial-mcp:0.1.0 .
docker inspect equatorial-mcp:0.1.0 --format '{{.Config.User}} {{json .Config.Entrypoint}}'

O diretório de estado permanece em /home/pptruser/.equatorial-mcp e deve ser persistido em volume — sem ele, sessão e artefatos se perdem. Segredos entram somente em runtime via arquivo de ambiente do orquestrador; nenhum segredo vai em build. O exemplo examples/docker-compose.yml usa init: true, stdin_open: true, volume nomeado para o caminho de estado, env_file sem valores e a capacidade documentada exigida pela imagem oficial. Quando local_path apontar para dentro do container, monte o volume correspondente ou use a resource_uri, que continua válida na sessão MCP.

Antes de criar qualquer tag de release, estes gates precisam de decisão do mantenedor e não podem ser adivinhados: disponibilidade/autorização do nome no registro público (ou decisão de fallback com escopo), remoto público canônico para metadados de repositório, digest imutável da imagem base do navegador, SHAs imutáveis das actions fixadas, chave pública de assinatura no segredo de ambiente RELEASE_SIGNING_PUBKEY com fingerprint fixado no workflow (2D413C35D5BAF50B95DAA3DE91257A029C418CF8), ordem de publicação (a imagem só publica após o tarball; em falha, repita o job de imagem no mesmo commit e nunca republicar o npm — depreciar, nunca sobrescrever), errata de recursos ainda aberta, confirmação de performance do parser na máquina de release, revisão vigente de termos e autorização, e validação nos três hosts. Nenhuma tag é criada com gate pendente.

  • O tratamento ocorre para atender ao pedido explícito do titular/operador autenticado. Não acesse conta de terceiros, não compartilhe credenciais e não explore endpoints não públicos.

  • Revise os termos de uso do portal antes de automatizar: se o portal proibir automação ou exigir consentimento adicional, o adaptador deve falhar de forma explícita e o canal oficial deve ser usado.

  • Sem telemetria, sem analytics e sem upload de documentos. O parser trabalha em memória e não persiste texto bruto extraído.

  • Logs usam allowlist de campos e mascaram a UC (no máximo os quatro últimos dígitos). CPF, nascimento, cookies, cabeçalhos de autenticação, corpo do PDF, texto integral da fatura e códigos de pagamento completos nunca entram em logs.

  • Reporte vulnerabilidades de forma privada conforme SECURITY.md, sem anexar documentos reais, capturas com dados pessoais, cookies, credenciais, endereços ou códigos de pagamento.

O que o software NÃO faz

  • Pagamento, confirmação de pagamento, geração de PIX, alteração cadastral ou qualquer operação mutável.

  • Envio por WhatsApp, e-mail, SMS ou qualquer canal de disseminação do documento.

  • Agendamento, monitoramento de novas contas ou operação como daemon de cobrança.

  • Bypass de CAPTCHA/WAF, proxy rotativo ou falsificação de fingerprint.

  • API HTTP pública, multi-tenancy ou armazenamento remoto.

  • OCR em nuvem, chave de IA ou processamento remoto de PDF.

Compatibilidade de hosts (2026-09-07)

Host

Arquivo de exemplo

Chaves de schema

Estado

Claude Code

examples/mcp.json

mcpServers.equatorial (command npx, args ["-y", "equatorial-mcp"], env vazio)

sintaxe validada localmente (JSON + formato); execução no host pendente de validação de release

Cursor

examples/mcp.json

mcpServers.equatorial (mesmo formato stdio acima)

sintaxe validada localmente (JSON + formato); execução no host pendente de validação de release

OpenCode

examples/opencode.jsonc

mcp.servers.equatorial (type: "local", command em array)

sintaxe validada localmente (JSONC + formato); execução no host pendente de validação de release

Somente esses três hosts têm exemplos neste repositório; nenhum outro host é declarado suportado. Os formatos evoluem fora deste projeto — cada release revalida os exemplos contra a documentação oficial e as versões instaladas antes de declarar execução confirmada.

Validação de release

  • Evals da Skill em evals/ (casos sintéticos + sequências de tools esperadas), verificados pelo contrato npx vitest run tests/contract/skill-evals.test.ts: nenhuma chamada real, nenhum modelo, nenhuma rede.

  • Candidato offline antes de qualquer validação manual: npm ci, npm run lint, npm run typecheck, npm run test:coverage, npm run build, npm pack --dry-run --json, probe de performance do parser com RUN_PARSER_PERF=1 npx vitest run tests/unit/fatura-parser.performance.test.ts e build da imagem candidata com docker build --pull=false -t equatorial-mcp:0.1.0-rc ..

  • Evidência de produção: nenhuma. O mapa de aliases de produção permanece vazio (a conveniência UC/mês segue baseada em nulos), o parser SCEE segue validado apenas por fixtures sintéticas e nenhuma amostra real foi registrada.

  • A tag v0.1.0 está BLOQUEADA até a execução humana autorizada do runbook em evals/README.md (portões de entrada, orçamento de requisições, evidência privada, condições de parada e rollback) e a quitação dos demais portões pré-tag: errata do código de Resource, autorização de nome/escopo no registro, digests de imagem e actions, e revisão de termos e autorização.

Limitações

  • Somente Goiás no v0.1.0; PA/MA falham explicitamente com UF_UNSUPPORTED.

  • Sem pagamento, sem alteração cadastral, sem envio por WhatsApp ou e-mail.

  • Sem bypass de CAPTCHA ou WAF. Em bloqueio, pare e tente manualmente depois.

  • PDF escaneado/sem texto continua disponível como Resource, mas os campos voltam null com parse_status: unreadable — sem OCR no MVP.

  • Clientes MCP sem bom suporte a Resources ainda recebem local_path quando acessível; nunca base64 inline na resposta inicial.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MLAN1O/equatorial-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server