GoodWe MCP Server
Implementação do servidor MCP para inversores GoodWe
Documentação
GoodWe Inverter MCP Server
Servidor MCP para monitoramento e controle de inversores solares GoodWe pela rede local.
Construído sobre a biblioteca goodwe e o SDK Python do Model Context Protocol.
Baseado na integração GoodWe do Home Assistant — as definições de sensores, modos de operação, configurações e suporte a famílias de inversores são modelados diretamente a partir dessa implementação.
Recursos
- Leitura de dados de operação em tempo real: produção fotovoltaica, estado da bateria, importação/exportação da rede, consumo de carga
- Leitura e escrita de todas as configurações ajustáveis do inversor
- Alternância de modos de operação (geral, eco, backup, peak-shaving, off-grid, …)
- Controle do limite de exportação para a rede e profundidade de descarga da bateria
- 7 recursos MCP: status, operação, configurações, fluxo de energia, energia diária, bateria, catálogo de sensores
- 6 modelos de prompt integrados para fluxos de trabalho comuns (visão geral de status, diagnósticos, otimização, …)
- Autenticação por token Bearer para todos os transportes HTTP
- Quatro modos de transporte: stdio, SSE, Streamable HTTP e servidor (SSE + Streamable HTTP combinados)
- Conexão automática via variáveis de ambiente
Requisitos
- Python 3.10+
- Inversor GoodWe acessível na rede local (porta UDP 8899 ou Modbus/TCP porta 502)
Instalação
# with uv (recommended) — installs the locked dependency set from uv.lock
uv sync
# or a plain (editable) install
uv pip install -e .
Uso
stdio (Claude Desktop)
goodwe-mcp
Adicione ao ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"goodwe": {
"command": "goodwe-mcp",
"env": {
"GOODWE_HOST": "192.168.1.100"
}
}
}
}
Transporte SSE
goodwe-mcp --transport sse --port 8080
# Server listens on http://0.0.0.0:8080/sse
Transporte Streamable HTTP
goodwe-mcp --transport streamable-http --port 8080
# Server listens on http://0.0.0.0:8080/mcp
Transporte servidor (SSE + Streamable HTTP combinados)
Atende ambos os transportes em uma única porta — útil quando você precisa suportar clientes SSE legados e clientes Streamable HTTP modernos simultaneamente.
goodwe-mcp --transport server --host 0.0.0.0 --port 8080
# SSE: http://0.0.0.0:8080/sse (GET) and /messages/ (POST)
# Streamable HTTP: http://0.0.0.0:8080/mcp
Opções
--transport {stdio,sse,streamable-http,server} Transport mode (default: stdio)
--host HOST Bind address for SSE/HTTP (default: 127.0.0.1)
--port PORT Listen port for SSE/HTTP (default: 8000)
--log-level {DEBUG,INFO,WARNING,ERROR} Logging verbosity (default: INFO)
--auth-token TOKEN Bearer token required on all HTTP requests (env: MCP_AUTH_TOKEN)
--base-url URL Public base URL, e.g. https://mcp.example.com (env: MCP_BASE_URL)
--allowed-hosts HOSTS Comma-separated Host header values to accept (env: MCP_ALLOWED_HOSTS)
--allowed-origins ORIGINS Comma-separated Origin header values to accept (env: MCP_ALLOWED_ORIGINS)
Variáveis de ambiente
| Variável | Descrição | Padrão |
|---|---|---|
GOODWE_HOST | IP/hostname do inversor para conexão automática na inicialização | — |
GOODWE_PORT | Porta UDP/TCP do inversor | 8899 |
GOODWE_FAMILY | Sobrescrita da família do inversor (ET, EH, BT, BH, ES, EM, BP, DT, MS, NS, XS) | detecção automática |
MCP_AUTH_TOKEN | Token Bearer exigido em todas as requisições HTTP | — (autenticação desativada) |
MCP_BASE_URL | URL base pública do servidor (usada como URL do emissor OAuth) | http://<host>:<port> |
MCP_ALLOWED_HOSTS | Valores de cabeçalho Host separados por vírgula a aceitar; ativa proteção contra DNS-rebinding em binds não-loopback | — (proteção apenas em binds loopback) |
MCP_ALLOWED_ORIGINS | Valores de cabeçalho Origin separados por vírgula a aceitar (clientes baseados em navegador) | — |
MCP_PORT | Porta de escuta usada pelo comando padrão do contêiner e pela verificação de saúde (somente Docker) | 8000 |
Autenticação
A autenticação por token Bearer é suportada para todos os transportes HTTP (sse, streamable-http, server).
Quando ativada, toda requisição MCP deve incluir um cabeçalho Authorization: Bearer <token>.
O endpoint /health permanece sempre desprotegido para que as sondas do Kubernetes continuem funcionando.
Ativar via variável de ambiente (recomendado)
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
goodwe-mcp --transport server --host 0.0.0.0 --port 8080
Ativar via flag de CLI
goodwe-mcp --transport streamable-http --port 8080 --auth-token my-secret-token
Configuração do Claude Desktop / cliente MCP
Adicione o token à configuração do servidor MCP do seu cliente. Por exemplo, com o Claude Desktop
usando o transporte streamable-http por meio de um proxy que injeta o cabeçalho, ou com qualquer
cliente que suporte cabeçalhos Authorization:
{
"mcpServers": {
"goodwe": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer my-secret-token"
}
}
}
}
Docker / Docker Compose
Passe o token pelo ambiente:
MCP_AUTH_TOKEN=my-secret-token GOODWE_HOST=192.168.1.100 \
docker compose -f docs/docker-compose.yml up -d
Kubernetes
Defina o token em docs/k8s/secret.yaml antes de aplicar os manifests:
stringData:
GOODWE_HOST: "192.168.1.100"
MCP_AUTH_TOKEN: "my-secret-token"
Se MCP_AUTH_TOKEN estiver vazio ou não definido, a autenticação é desativada e todos os endpoints HTTP ficam publicamente acessíveis. O servidor registrará um aviso na inicialização quando vinculado a um endereço não-loopback sem token.
Proteção contra DNS rebinding
O SDK MCP valida automaticamente os cabeçalhos Host e Origin quando o servidor está vinculado a um endereço loopback. Para qualquer outro endereço de bind (incluindo o padrão do Docker 0.0.0.0), liste os nomes de host ou pares host:port que os clientes usam legitimamente para acessar o servidor:
MCP_ALLOWED_HOSTS="mcp.example.com,192.168.1.10:8000" \
goodwe-mcp --transport server --host 0.0.0.0 --port 8000
Requisições cujo cabeçalho Host não esteja listado são rejeitadas com HTTP 421. Um sufixo :* corresponde a qualquer porta (ex.: 192.168.1.10:*). Use MCP_ALLOWED_ORIGINS para permitir adicionalmente clientes baseados em navegador de origens específicas. O servidor registra um aviso na inicialização quando vinculado a um endereço não-loopback sem essa configuração.
TLS / HTTPS
O próprio servidor MCP não encerra TLS. Para qualquer implantação fora de localhost, coloque um proxy reverso com terminação TLS na frente dele (nginx, Caddy, Traefik). Servir dados do inversor — que constituem dados pessoais sob o GDPR quando vinculados a um domicílio — por HTTP simples é um risco de segurança.
Exemplo com Caddy (opção mais simples):
mcp.example.com {
reverse_proxy localhost:8000
}
Aviso de Processamento de Dados
Este servidor processa dados de um inversor solar GoodWe, incluindo o endereço IP do inversor, número de série e métricas de consumo de energia. Quando implantado em uma residência e operado pelo proprietário para uso pessoal, esse processamento se enquadra na isenção doméstica do GDPR (Art. 2(2)(c)) e o GDPR não se aplica. Se implantado comercialmente — por exemplo, para monitorar inversores pertencentes a clientes terceiros — o operador se torna controlador de dados sob o GDPR (UE) 2016/679 e deve estabelecer uma base legal para o processamento (Art. 6), manter registros das atividades de processamento (Art. 30) e garantir medidas técnicas e organizacionais adequadas (Art. 32), incluindo criptografia TLS e controle de acesso.
Docker
Build
Build para a arquitetura da máquina atual:
docker build -t goodwe-inverter-mcp:latest .
Builds multiplataforma (amd64 + arm64)
Use docker buildx para produzir uma imagem que rode tanto em servidores x86-64 quanto em placas ARM (Raspberry Pi, Apple Silicon, AWS Graviton, etc.).
Configuração única — crie um builder que suporte compilação cruzada:
docker buildx create --name multi --driver docker-container --bootstrap --use
Build de ambas as plataformas e carregamento no daemon local — requer o armazenamento de imagens containerd (ativado por padrão no Docker Desktop 4.34+; no Linux execute dockerd --snapshotter=overlayfs ou ative em /etc/docker/daemon.json):
docker buildx build --platform linux/amd64,linux/arm64 -t goodwe-inverter-mcp:latest --load .
Build de ambas as plataformas e envio para um registry (ex.: Docker Hub ou GHCR):
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t youruser/goodwe-inverter-mcp:latest \
--push .
Build de ambas as plataformas e exportação como tar OCI local (sem necessidade de registry):
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t goodwe-inverter-mcp:latest \
--output type=oci,dest=goodwe-inverter-mcp.tar .
Execução
docker run -d \
--name goodwe-mcp \
-e GOODWE_HOST=192.168.1.100 \
-p 8000:8000 \
goodwe-inverter-mcp:latest
O contêiner usa por padrão --transport server (SSE + Streamable HTTP na porta 8000).
Altere a porta com MCP_PORT para que a verificação de saúde integrada a acompanhe:
docker run -d -e GOODWE_HOST=192.168.1.100 -e MCP_PORT=9000 -p 9000:9000 \
goodwe-inverter-mcp:latest
Para alterar o transporte, sobrescreva o comando. Mantenha MCP_PORT em sincronia, pois a verificação de saúde o testa:
docker run -d -e GOODWE_HOST=192.168.1.100 -e MCP_PORT=9000 -p 9000:9000 \
goodwe-inverter-mcp:latest \
goodwe-mcp --transport streamable-http --host 0.0.0.0 --port 9000
Docker Compose
GOODWE_HOST=192.168.1.100 docker compose -f docs/docker-compose.yml up -d
docs/docker-compose.yml usa network_mode: host por padrão para que o contêiner alcance o inversor na LAN local. Remova essa linha se sua rede já roteia tráfego LAN para contêineres.
Kubernetes
Pré-requisitos
O inversor GoodWe se comunica por UDP/TCP na rede local. O pod precisa alcançar o IP do inversor. A configuração mais simples é hostNetwork: true em um nó na mesma sub-rede; remova-a se seu cluster tiver rede plana ou outra solução de roteamento.
Implantação
# 1. Edit the inverter IP
vi docs/k8s/secret.yaml
# 2. Apply all manifests
kubectl apply -f docs/k8s/
# 3. Check status
kubectl rollout status deployment/goodwe-mcp
kubectl logs -f deployment/goodwe-mcp
Endpoints de saúde
Tanto as sondas de liveness quanto as de readiness acessam GET /health, que retorna:
{ "status": "ok", "inverter_connected": true }
O pod fica pronto assim que o servidor HTTP está ativo. inverter_connected será false até que o servidor se conecte com sucesso ao inversor (a conexão automática é disparada na primeira sessão de cliente MCP).
Ferramentas
| Ferramenta | Descrição |
|---|---|
connect_inverter | Conectar a um inversor GoodWe por IP/host |
get_connection_status | Verificar se está conectado e mostrar informações do dispositivo |
get_device_info | Nome do modelo, número de série, versão do firmware |
get_runtime_data | Todos os valores de sensores em tempo real (filtro opcional por tipo: PV/AC/BAT/GRID/UPS/BMS) |
list_sensors | Listar todos os IDs e nomes de sensores |
read_sensor | Ler um único sensor por ID |
get_settings_data | Todas as configurações ajustáveis e valores atuais |
read_setting | Ler uma única configuração por ID |
write_setting | Escrever um valor em uma configuração ajustável |
get_operation_mode | Modo atual e modos suportados |
set_operation_mode | Definir modo: geral, eco, backup, off_grid, peak_shaving, eco_charge, eco_discharge |
get_grid_export_limit | Limite de exportação para a rede em watts |
set_grid_export_limit | Definir limite de exportação para a rede (0 = desativado) |
get_battery_dod | Configuração de profundidade de descarga da bateria |
set_battery_dod | Definir profundidade de descarga da bateria (0–99%) |
Prompts
Modelos de prompt pré-escritos que clientes MCP podem buscar e usar diretamente.
| Prompt | Argumentos | Descrição |
|---|---|---|
status_overview | — | Relatório completo de status: conexão, fluxo de energia em tempo real, bateria, rede |
battery_optimisation | — | Revisar configurações da bateria e sugerir melhorias de DoD / modo |
grid_export_config | — | Verificar e ajustar o limite de exportação de energia para a rede |
operation_mode_change | — | Explicar os modos disponíveis e ajudar a alternar para o correto |
diagnose_issue | symptom (opcional) | Coletar diagnósticos completos e identificar problemas |
daily_energy_summary | — | Contadores de energia de hoje como tabela legível |
Recursos
| URI | Descrição |
|---|---|
inverter://status | Status da conexão e informações do dispositivo (JSON) |
inverter://runtime | Todos os valores de sensores em tempo real (JSON) |
inverter://settings | Todas as configurações ajustáveis (JSON) |
inverter://power/now | Fluxo de energia em tempo real — PV, bateria, rede e carga em watts, agrupados por tipo |
inverter://energy/today | Contadores de energia de hoje — produção, carga, compra/venda da rede, carga/descarga da bateria (kWh) |
inverter://battery | Sensores da bateria, limite de DoD e modo de operação atual em um único payload |
inverter://sensors | Catálogo estático de sensores — id, nome, unidade e tipo de cada sensor (sem valores em tempo real) |
Famílias de inversores
| Família | Modelos | Observações |
|---|---|---|
| ET / EH / BT / BH | Híbrido trifásico | Suporte a bateria, até 4 strings fotovoltaicos |
| ES / EM / BP | Híbrido monofásico | Suporte a bateria |
| DT / MS / NS / XS | Somente grid-tie | Sem bateria |
Desenvolvimento
# install runtime + dev dependencies from the lockfile
uv sync
# run the test suite
uv run pytest
# with a coverage report
uv run pytest --cov=goodwe_mcp --cov-report=term-missing
# lint
uv run ruff check src tests
Os testes são executados inteiramente contra um inversor fake em memória, portanto não é necessário acesso a hardware ou rede. Eles conduzem o servidor por uma sessão real de cliente MCP e pelos aplicativos ASGI reais, cobrindo validação de entrada de ferramentas, payloads de recursos, autenticação Bearer, proteção contra DNS-rebinding e a CLI. O GitHub Actions executa a suíte em Python 3.10 a 3.13 e cria a imagem do contêiner a cada push e pull request.
Licença
Consulte o arquivo LICENSE na raiz do repositório.
Aviso legal
Este software não é afiliado nem endossado pela GoodWe Inc. Use por sua conta e risco. Este software é um projeto pessoal que mantenho no meu tempo livre. Consulte a licença para mais informações.