GoodWe MCP Server

Implementación del servidor MCP para inversores GoodWe

Documentación

Servidor MCP de GoodWe Inverter

Servidor MCP para monitorear y controlar inversores solares GoodWe a través de la red local.

goodwe-inverter-mcp MCP server

Construido sobre la librería goodwe y el SDK de Python del Model Context Protocol.

Basado en la integración de GoodWe para Home Assistant — las definiciones de sensores, modos de operación, ajustes y soporte de familias de inversores están modelados directamente según esa implementación.

Características

  • Lee datos de operación en tiempo real: producción fotovoltaica, estado de la batería, importación/exportación de red, consumo de carga
  • Lee y escribe todos los ajustes configurables del inversor
  • Cambia modos de operación (general, eco, respaldo, reducción de picos, fuera de red, …)
  • Controla el límite de exportación a la red y la profundidad de descarga de la batería
  • 7 recursos MCP: estado, operación, ajustes, flujo de energía, energía diaria, batería, catálogo de sensores
  • 6 plantillas de prompt integradas para flujos de trabajo comunes (resumen de estado, diagnóstico, optimización, …)
  • Autenticación con token Bearer para todos los transportes HTTP
  • Cuatro modos de transporte: stdio, SSE, HTTP Streamable y servidor (SSE + HTTP Streamable combinados)
  • Conexión automática mediante variables de entorno

Requisitos

  • Python 3.10+
  • Inversor GoodWe accesible en la red local (puerto UDP 8899 o Modbus/TCP puerto 502)

Instalación

# 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

Añade a ~/.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 HTTP Streamable

goodwe-mcp --transport streamable-http --port 8080
# Server listens on http://0.0.0.0:8080/mcp

Transporte de servidor (SSE + HTTP Streamable combinados)

Sirve ambos transportes en un único puerto — útil cuando necesitas soportar clientes SSE heredados y clientes HTTP Streamable modernos simultáneamente.

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

Opciones

--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)

Variables de entorno

VariableDescripciónValor predeterminado
GOODWE_HOSTIP / hostname del inversor para conexión automática al inicio
GOODWE_PORTPuerto UDP/TCP del inversor8899
GOODWE_FAMILYAnulación de familia de inversor (ET, EH, BT, BH, ES, EM, BP, DT, MS, NS, XS)detección automática
MCP_AUTH_TOKENToken Bearer requerido en todas las solicitudes HTTP— (autenticación deshabilitada)
MCP_BASE_URLURL base pública del servidor (usada como URL del emisor OAuth)http://<host>:<port>
MCP_ALLOWED_HOSTSValores de cabecera Host separados por comas a aceptar; habilita la protección contra rebinding de DNS en enlaces no-loopback— (protección solo en enlaces loopback)
MCP_ALLOWED_ORIGINSValores de cabecera Origin separados por comas a aceptar (clientes basados en navegador)
MCP_PORTPuerto de escucha usado por el comando predeterminado del contenedor y la verificación de salud (solo Docker)8000

Autenticación

La autenticación con token Bearer es compatible con todos los transportes HTTP (sse, streamable-http, server). Cuando está habilitada, cada solicitud MCP debe incluir una cabecera Authorization: Bearer <token>. El endpoint /health siempre está desprotegido para que las sondas de Kubernetes sigan funcionando.

Habilitar mediante variable de entorno (recomendado)

export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
goodwe-mcp --transport server --host 0.0.0.0 --port 8080

Habilitar mediante bandera de CLI

goodwe-mcp --transport streamable-http --port 8080 --auth-token my-secret-token

Configuración de Claude Desktop / cliente MCP

Añade el token a la configuración del servidor MCP de tu cliente. Por ejemplo, con Claude Desktop usando el transporte streamable-http a través de un proxy que inyecta la cabecera, o con cualquier cliente que soporte cabeceras Authorization:

{
  "mcpServers": {
    "goodwe": {
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer my-secret-token"
      }
    }
  }
}

Docker / Docker Compose

Pasa el token a través del entorno:

MCP_AUTH_TOKEN=my-secret-token GOODWE_HOST=192.168.1.100 \
  docker compose -f docs/docker-compose.yml up -d

Kubernetes

Establece el token en docs/k8s/secret.yaml antes de aplicar los manifiestos:

stringData:
  GOODWE_HOST: "192.168.1.100"
  MCP_AUTH_TOKEN: "my-secret-token"

Si MCP_AUTH_TOKEN está vacío o no está configurado, la autenticación está deshabilitada y todos los endpoints HTTP son accesibles públicamente. El servidor registrará una advertencia al inicio cuando esté vinculado a una dirección no-loopback sin token.

Protección contra rebinding de DNS

El SDK de MCP valida las cabeceras Host y Origin automáticamente cuando el servidor está vinculado a una dirección loopback. Para cualquier otra dirección de enlace (incluido el valor predeterminado de Docker 0.0.0.0), lista los hostnames o pares host:port que los clientes usan legítimamente para alcanzar el servidor:

MCP_ALLOWED_HOSTS="mcp.example.com,192.168.1.10:8000" \
  goodwe-mcp --transport server --host 0.0.0.0 --port 8000

Las solicitudes cuya cabecera Host no esté listada se rechazan con HTTP 421. Un sufijo :* coincide con cualquier puerto (por ejemplo, 192.168.1.10:*). Usa MCP_ALLOWED_ORIGINS para permitir adicionalmente clientes basados en navegador desde orígenes específicos. El servidor registra una advertencia al inicio cuando está vinculado a una dirección no-loopback sin esta configuración.

TLS / HTTPS

El servidor MCP en sí no termina TLS. Para cualquier despliegue fuera de localhost, coloca un proxy inverso con terminación TLS delante (nginx, Caddy, Traefik). Servir datos del inversor — que constituyen datos personales según el RGPD cuando se vinculan a un hogar — a través de HTTP plano es un riesgo de seguridad.

Ejemplo con Caddy (la opción más simple):

mcp.example.com {
    reverse_proxy localhost:8000
}

Aviso de procesamiento de datos

Este servidor procesa datos de un inversor solar GoodWe, incluida la dirección IP del inversor, el número de serie y las métricas de consumo de energía. Cuando se despliega en un hogar y lo opera el propietario para uso personal, este procesamiento cae bajo la exención doméstica del RGPD (Art. 2(2)(c)) y el RGPD no aplica. Si se despliega comercialmente — por ejemplo, para monitorear inversores pertenecientes a clientes de terceros — el operador se convierte en responsable del tratamiento según el RGPD (UE) 2016/679 y debe establecer una base legal para el procesamiento (Art. 6), mantener registros de las actividades de procesamiento (Art. 30) y garantizar medidas técnicas y organizativas apropiadas (Art. 32), incluido el cifrado TLS y el control de acceso.

Docker

Construcción

Construye para la arquitectura de la máquina actual:

docker build -t goodwe-inverter-mcp:latest .

Construcciones multi-plataforma (amd64 + arm64)

Usa docker buildx para producir una imagen que se ejecute tanto en servidores x86-64 como en placas ARM (Raspberry Pi, Apple Silicon, AWS Graviton, etc.).

Configuración única — crea un constructor que soporte compilación cruzada:

docker buildx create --name multi --driver docker-container --bootstrap --use

Construye ambas plataformas y carga en el daemon local — requiere el almacén de imágenes containerd (habilitado por defecto en Docker Desktop 4.34+; en Linux ejecuta dockerd --snapshotter=overlayfs o habilítalo en /etc/docker/daemon.json):

docker buildx build --platform linux/amd64,linux/arm64 -t goodwe-inverter-mcp:latest --load .

Construye ambas plataformas y envía a un registro (por ejemplo, Docker Hub o GHCR):

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t youruser/goodwe-inverter-mcp:latest \
  --push .

Construye ambas plataformas y exporta como un tar OCI local (sin necesidad de registro):

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t goodwe-inverter-mcp:latest \
  --output type=oci,dest=goodwe-inverter-mcp.tar .

Ejecución

docker run -d \
  --name goodwe-mcp \
  -e GOODWE_HOST=192.168.1.100 \
  -p 8000:8000 \
  goodwe-inverter-mcp:latest

El contenedor usa por defecto --transport server (SSE + HTTP Streamable en el puerto 8000).

Cambia el puerto con MCP_PORT para que la verificación de salud integrada lo siga:

docker run -d -e GOODWE_HOST=192.168.1.100 -e MCP_PORT=9000 -p 9000:9000 \
  goodwe-inverter-mcp:latest

Para cambiar el transporte, anula el comando. Mantén MCP_PORT sincronizado, ya que la verificación de salud lo sondea:

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 defecto para que el contenedor pueda alcanzar el inversor en la LAN local. Elimina esa línea si tu red ya enruta el tráfico LAN hacia los contenedores.

Kubernetes

Requisitos previos

El inversor GoodWe se comunica por UDP/TCP en la red local. El pod necesita alcanzar la IP del inversor. La configuración más simple es hostNetwork: true en un nodo de la misma subred; elimínala si tu clúster tiene redes planas u otra solución de enrutamiento.

Despliegue

# 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 salud

Tanto las sondas de liveness como de readiness apuntan a GET /health, que devuelve:

{ "status": "ok", "inverter_connected": true }

El pod se vuelve listo una vez que el servidor HTTP está activo. inverter_connected será false hasta que el servidor se conecte exitosamente al inversor (la conexión automática se dispara en la primera sesión de cliente MCP).

Herramientas

HerramientaDescripción
connect_inverterConecta a un inversor GoodWe por IP/hostname
get_connection_statusVerifica si está conectado y muestra información del dispositivo
get_device_infoNombre del modelo, número de serie, versión de firmware
get_runtime_dataTodos los valores de sensores en vivo (filtro opcional por tipo: PV/AC/BAT/GRID/UPS/BMS)
list_sensorsLista todos los IDs y nombres de sensores
read_sensorLee un solo sensor por ID
get_settings_dataTodos los ajustes configurables y valores actuales
read_settingLee un solo ajuste por ID
write_settingEscribe un valor en un ajuste configurable
get_operation_modeModo actual y modos soportados
set_operation_modeEstablece modo: general, eco, respaldo, fuera_de_red, reducción_de_picos, eco_carga, eco_descarga
get_grid_export_limitLímite de exportación a la red en vatios
set_grid_export_limitEstablece límite de exportación a la red (0 = deshabilitado)
get_battery_dodAjuste de profundidad de descarga de la batería
set_battery_dodEstablece profundidad de descarga de la batería (0–99%)

Prompts

Plantillas de prompt preescritas que los clientes MCP pueden obtener y usar directamente.

PromptArgumentosDescripción
status_overviewInforme de estado completo: conexión, flujo de energía en vivo, batería, red
battery_optimisationRevisa los ajustes de la batería y sugiere mejoras de DoD / modo
grid_export_configVerifica y ajusta el límite de potencia de exportación a la red
operation_mode_changeExplica los modos disponibles y ayuda a cambiar al correcto
diagnose_issuesymptom (opcional)Recopila diagnóstico completo e identifica problemas
daily_energy_summaryContadores de energía de hoy como tabla legible

Recursos

URIDescripción
inverter://statusEstado de conexión e información del dispositivo (JSON)
inverter://runtimeTodos los valores de sensores en vivo (JSON)
inverter://settingsTodos los ajustes configurables (JSON)
inverter://power/nowFlujo de energía en tiempo real — PV, batería, red y carga en vatios, agrupados por tipo
inverter://energy/todayContadores de energía de hoy — producción, carga, compra/venta de red, carga/descarga de batería (kWh)
inverter://batterySensores de batería, límite de DoD y modo de operación actual en un solo payload
inverter://sensorsCatálogo estático de sensores — id, nombre, unidad y tipo para cada sensor (sin valores en vivo)

Familias de inversores

FamiliaModelosNotas
ET / EH / BT / BHHíbrido trifásicoSoporte de batería, hasta 4 cadenas PV
ES / EM / BPHíbrido monofásicoSoporte de batería
DT / MS / NS / XSSolo conexión a redSin batería

Desarrollo

# 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

Las pruebas se ejecutan completamente contra un inversor simulado en memoria, por lo que no se necesita acceso a hardware ni red. Conducen el servidor a través de una sesión real de cliente MCP y las aplicaciones ASGI reales, cubriendo la validación de entrada de herramientas, payloads de recursos, autenticación Bearer, protección contra rebinding de DNS y la CLI. GitHub Actions ejecuta la suite en Python 3.10 hasta 3.13 y construye la imagen del contenedor en cada push y pull request.

Licencia

Consulta el archivo LICENSE en la raíz del repositorio.

Aviso legal

Este software no está afiliado ni respaldado por GoodWe Inc. Úsalo bajo tu propio riesgo. Este software es un proyecto personal que mantengo en mi tiempo libre. Consulta la licencia para más información.