GREE MCP Server

Servidor MCP local en LAN para controlar aires acondicionados WiFi GREE / EWPE mediante su protocolo UDP nativo. Sin nube. Transportes stdio + HTTP.

Documentación

gree-ac-mcp-server

gree-ac-mcp-server – quality and maintenance score on Glama

Un servidor de Protocolo de Contexto de Modelo (MCP) que controla aires acondicionados WiFi compatibles con GREE / EWPE directamente a través de su protocolo UDP nativo. Sin Homebridge, sin nube: se comunica directamente con las unidades en tu red local.

El protocolo de cable de GREE (cifrado AES, flujo de escaneo/vínculo/estado/comando) está implementado desde cero basado en eibenp/homebridge-gree-airconditioner y tomikaa87/gree-remote.

  • Dos transportes: MCP stdio (para Claude Desktop y otros clientes locales) y http (HTTP Streamable moderno y HTTP+SSE heredado), con autenticación bearer obligatoria en HTTP.
  • Ambos esquemas de cifrado: v1 (AES-128-ECB) y v2 (AES-128-GCM), detectados automáticamente por dispositivo.
  • Sondeo en segundo plano: cada dispositivo está vinculado y sondeado continuamente; las llamadas a herramientas se ejecutan de inmediato y el siguiente sondeo confirma el nuevo estado.

Requisitos

  • Node.js >= 22
  • Las unidades de aire acondicionado deben estar en la misma LAN/subred que el servidor (broadcast/unicast UDP al puerto 7000).

Instalación y compilación

npm install
npm run build

Inicio rápido

cp config.example.json config.json
# edit config.json: set bearerToken and your devices' mac/address

# stdio (local MCP clients)
npm run start:stdio -- --config ./config.json

# HTTP (network clients)
npm run start:http -- --config ./config.json --host 0.0.0.0 --port 8080

Durante el desarrollo puedes ejecutar TypeScript directamente con npm run dev:stdio / npm run dev:http.

Banderas de CLI

BanderaPredeterminadoDescripción
--transport <stdio|http>stdioModo de transporte.
--config <path>$GREE_MCP_CONFIGRuta al archivo de configuración. Requerido para modo HTTP; opcional para stdio (inicia sin dispositivos si se omite).
--host <host>0.0.0.0Host de enlace HTTP (modo http). Anula la configuración. Debe ser no vacío.
--port <port>8080Puerto de enlace HTTP (modo http). Anula la configuración. Debe ser un entero 1–65535.
--log-level <debug|info|warn|error>infoVerbosidad de registro (líneas JSON en stderr).

La ruta del archivo de configuración también puede proporcionarse mediante la variable de entorno GREE_MCP_CONFIG.


Configuración

Archivo JSON validado con zod. En un error de validación, el servidor imprime el campo/dispositivo problemático y sale con código no cero. El servidor debe reiniciarse para aplicar los cambios de configuración (la recarga en caliente no está implementada).

Campos de nivel superior

CampoTipoPredeterminadoNotas
bearerTokenstring— (requerido)Token requerido en cada solicitud HTTP/SSE. Mínimo 32 caracteres. Ignorado en modo stdio.
udpPortnumber7000Puerto UDP en el que escuchan los dispositivos.
updateIntervalnumber (ms)1000Intervalo de sondeo de estado predeterminado.
retryIntervalnumber (ms)5000Intervalo de reintento/detección de desconexión predeterminado.
hoststring0.0.0.0Host de enlace HTTP predeterminado (anulable por --host).
portnumber8080Puerto de enlace HTTP predeterminado (anulable por --port).
corsOriginsstring[][]Orígenes CORS permitidos para modo HTTP. Vacío desactiva CORS; ["*"] permite cualquier origen; de lo contrario, una lista de permitidos explícita.
devicesarray— (requerido, ≥1)Una entrada por aire acondicionado. Se rechazan valores duplicados de mac y duplicados de name (sin distinción de mayúsculas/minúsculas).

Campos por dispositivo

CampoTipoPredeterminadoNotas
namestring— (requerido)Nombre descriptivo; utilizable como alias de selector de herramientas.
roomstring—Etiqueta de agrupación opcional.
addressstring IPv4—Si se establece, el servidor envía unicast a esa dirección. Si se omite, descubre la IP por MAC mediante broadcast UDP y luego la almacena en caché.
macstring— (requerido)12 caracteres hexadecimales (los separadores/mayúsculas se normalizan). Clave principal para todas las herramientas y el vínculo.
modelstring—Solo visualización.
nameFanstring—Nombre de ventilador separado opcional (solo visualización).
serialNumberstring—Opcional; reservado para identidad de caché de claves.
minimumTargetTemperaturenumber16Límite inferior para set_target_temperature.
maximumTargetTemperaturenumber30Límite superior para set_target_temperature.
oscillation.on/off.{horizontal,vertical}enumon=full, off=defaultPosiciones de oscilación aplicadas por set_oscillation. Ver enums abajo.
xFanbooleanfalseControla la herramienta set_xfan para este dispositivo.
lightControlbooleanfalseControla la herramienta set_light para este dispositivo.
fakeSensorbooleanfalseSi la unidad no tiene sensor real, estima la temperatura actual a partir del objetivo y marca estimated: true.
sensorOffsetnumber (°C)0Calibración añadida a la temperatura actual decodificada.
speedSteps3 | 55Pasos físicos del ventilador; se usa para mapear set_fan_speed hacia abajo en unidades de 3 velocidades.
encryptionVersion0 | 1 | 200 = detección automática, 1 = forzar ECB, 2 = forzar GCM.
updateIntervalnumber (ms)hereda del nivel superiorAnulación por dispositivo.
retryIntervalnumber (ms)hereda del nivel superiorAnulación por dispositivo.

Nota sobre sensorOffset y la decodificación de temperatura. La mayoría de las unidades GREE reportan el sensor interno (TemSen) como actual°C + 40. El servidor resta ese desplazamiento base fijo para decodificar, y luego añade tu sensorOffset como calibración adicional. Por lo tanto, currentTemperature = TemSen − 40 + sensorOffset.

Enums de posición de oscilación

  • Vertical (SwUpDn): default, full, fixed-top, fixed-upper-middle, fixed-middle, fixed-lower-middle, fixed-bottom (más swing-top/swing-upper-middle/swing-middle/swing-lower-middle/swing-bottom).
  • Horizontal (SwingLfRig, solo en unidades con lamas horizontales): default, full, fixed-left, fixed-center-left, fixed-center, fixed-center-right, fixed-right.

Cómo obtener la MAC de un dispositivo

La MAC es el ID del dispositivo GREE (una cadena hexadecimal de 12 caracteres, p. ej. 502cc6aabbcc). Siguiendo el método documentado del plugin de homebridge, la forma más fácil es ejecutar este servidor (o el plugin de homebridge) con registro de depuración en la misma LAN y observar las respuestas de escaneo:

node dist/index.js --transport http --config ./config.json --log-level debug

Cada unidad descubierta registra una línea device discovered que contiene su mac, address, model y firmware version. Otras opciones:

  • Consulta la lista de clientes DHCP de tu router para la MAC del adaptador WiFi del aire acondicionado (quita los dos puntos, ponla en minúsculas).
  • Usa la aplicación oficial GREE+ / EWPE Smart, o cualquier utilidad de escaneo GREE, que reporta el ID del dispositivo.

Herramientas MCP

Cada herramienta acepta un selector de dispositivo: mac (canónico) o name (alias, comparado con la configuración). Proporciona uno de ellos. Las herramientas set_* envían el comando UDP inmediatamente y reportan "command sent"; el bucle de sondeo en segundo plano confirma el nuevo estado poco después. Si un dispositivo está desconectado/sin vínculo, las herramientas de escritura devuelven un error en lugar de tener éxito silenciosamente.

HerramientaEsquema de entradaDescripción
list_devices{}Todos los dispositivos configurados con estado online/bound y último modo/temperatura conocidos.
get_device_status{ mac?, name? }Estado decodificado completo de un dispositivo.
get_room_temperature{ mac?, name? }Temperatura actual calibrada (°C). Marca estimated: true cuando se deriva mediante fakeSensor.
set_power{ mac?, name?, on: boolean }Enciende/apaga la unidad.
set_mode{ mac?, name?, mode: "auto"|"cool"|"dry"|"fan"|"heat" }Establece el modo (también enciende).
set_target_temperature{ mac?, name?, temperature: number }Establece la temperatura objetivo en °C. Rechazada (no limitada) si está fuera del rango mínimo/máximo del dispositivo.
set_fan_speed{ mac?, name?, speed: "auto"|"quiet"|"low"|"medium-low"|"medium"|"medium-high"|"high"|"turbo" }Establece la velocidad del ventilador; quiet/turbo activan modos dedicados; se mapea hacia abajo en unidades de 3 velocidades.
set_oscillation{ mac?, name?, on: boolean }Aplica las posiciones de oscilación de encendido/apagado configuradas.
set_xfan{ mac?, name?, on: boolean }X-Fan / soplado. Solo utilizable cuando xFan: true.
set_light{ mac?, name?, on: boolean }Luz de pantalla. Solo utilizable cuando lightControl: true.
set_quiet_mode{ mac?, name?, on: boolean }Modo silencioso (activarlo desactiva turbo).
set_turbo_mode{ mac?, name?, on: boolean }Modo turbo/potente (activarlo desactiva silencioso).

Cada dispositivo también se expone como recurso en gree://device/<mac> que devuelve su estado decodificado como JSON.


Uso con Claude Desktop (stdio)

Añade a claude_desktop_config.json:

{
  "mcpServers": {
    "gree-ac": {
      "command": "node",
      "args": [
        "/absolute/path/to/gree-ac-mcp-server/dist/index.js",
        "--transport", "stdio",
        "--config", "/absolute/path/to/config.json"
      ]
    }
  }
}

No se necesita token bearer en modo stdio (la tubería del proceso es el límite de confianza).


Uso HTTP

La autenticación bearer es obligatoria en /mcp, /sse y /messages. Los tokens faltantes o inválidos reciben 401 con un encabezado WWW-Authenticate: Bearer. /healthz es sin autenticación y devuelve solo identificadores de dispositivo anonimizados (MAC solo con prefijo de proveedor, nombre solo con la primera letra).

CORS (clientes de navegador)

Para clientes MCP basados en navegador (p. ej. MCP Inspector), establece corsOrigins en la configuración. CORS está desactivado por defecto (sin encabezados añadidos), por lo que los clientes que no son de navegador como Claude Desktop no se ven afectados. Cuando está activado:

  • La verificación previa OPTIONS se responde antes de la autenticación (la verificación previa no lleva encabezado Authorization).
  • El encabezado de respuesta Mcp-Session-Id se expone para que el JS del cliente pueda leer el ID de sesión.
  • La autenticación aún se aplica en la solicitud real; solo los orígenes listados reciben un Access-Control-Allow-Origin.
// config.json
"corsOrigins": ["https://inspector.example.com"]   // or ["*"] to allow any origin

Verificación de salud

curl http://localhost:8080/healthz
# {"status":"ok","total":2,"bound":1,"unbound":1,
#  "devices":[{"mac":"502cc6******","name":"T***","bound":true}]}

HTTP Streamable moderno

Inicialización (nota el encabezado Accept requerido y que el ID de sesión regresa en un encabezado de respuesta):

curl -i -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
# -> response header:  Mcp-Session-Id: <uuid>

Luego reutiliza ese ID de sesión:

SID=<uuid-from-above>

# complete the handshake
curl -s -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" -H "Mcp-Session-Id: $SID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# list devices
curl -s -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" -H "Mcp-Session-Id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_devices","arguments":{}}}'

# turn a unit on
curl -s -X POST http://localhost:8080/mcp \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" -H "Mcp-Session-Id: $SID" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"set_power","arguments":{"mac":"502cc6aabbcc","on":true}}}'

HTTP+SSE heredado

# 1) open the event stream (keeps running; prints the "endpoint" event with your sessionId)
curl -N http://localhost:8080/sse -H "Authorization: Bearer YOUR_TOKEN"

# 2) post messages to the endpoint reported by the stream (sessionId from the endpoint event)
curl -X POST "http://localhost:8080/messages?sessionId=YOUR_SESSION_ID" \
  -H "Authorization: Bearer YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Docker

La imagen es de múltiples etapas y se ejecuta como el usuario no root node, con modo HTTP por defecto leyendo /config/config.json.

Imágenes precompiladas multi-arquitectura (linux/amd64 + linux/arm64) se publican en GitHub Container Registry:

docker run --rm \
  --network host \
  -v "$(pwd)/config.json:/config/config.json:ro" \
  ghcr.io/marcinn2/gree-ac-mcp:latest

O compílala tú mismo:

docker build -t gree-ac-mcp-server .
docker run --rm \
  --network host \
  -v "$(pwd)/config.json:/config/config.json:ro" \
  gree-ac-mcp-server

El descubrimiento/broadcast UDP necesita acceso L2 a la subred del aire acondicionado. --network host es la forma más simple de darle eso al contenedor en Linux; de lo contrario, establece el address de cada dispositivo explícitamente y asegúrate de que el enrutamiento UDP/7000 a las unidades funcione desde la red del contenedor.

El contenedor EXPOSEs 8080. Anula los argumentos del punto de entrada para cambiar transporte/puerto, p. ej. docker run ... gree-ac-mcp-server --transport http --config /config/config.json --port 9000.


Registro y seguridad

  • Los registros son líneas JSON en stderr (stdout está reservado para el canal MCP en modo stdio), incluyendo mac, action y outcome del dispositivo.
  • El token bearer y todas las claves AES/dispositivo nunca se registran.
  • Los registros contienen identificadores de dispositivo (mac, dirección IP). Al ejecutarse como servicio de larga duración (systemd, Docker, etc.), limita la retención con rotación de registros normal para que no se acumulen indefinidamente. Mantén el --log-level info predeterminado; debug registra más identificadores.
  • El modo HTTP usa autenticación bearer en texto plano. Ejecútalo solo en una LAN doméstica de confianza, o coloca un proxy inverso con terminación TLS (Caddy, nginx, …) delante — de lo contrario, el token y los datos de solicitud quedan expuestos en tránsito. El modo stdio no tiene exposición de red.

Esta es una herramienta autoalojada, personal/doméstica sin análisis, sin servicios de terceros y sin persistencia de datos en disco (el estado del dispositivo se mantiene solo en memoria). La configuración — incluyendo tu bearerToken y las MAC de los dispositivos — vive en tu config.json local, que .gitignore ya excluye del control de versiones.

Pruebas

npm test

Cubre el protocolo criptográfico (v1/v2 cifrado-descifrado de ida y vuelta y un vector de respuesta conocido, además del empaquetado/desempaquetado de sobres), la validación del esquema de configuración (valores predeterminados, normalización de MAC, herencia de intervalos, rechazo de MAC duplicados/nombres duplicados, valores incorrectos y rechazo de bearerToken corto), el middleware de autenticación bearer (acepta el token correcto; rechaza tokens incorrectos, de longitud no coincidente y malformados), y el middleware CORS (lista blanca de orígenes, comodín y manejo de preflight).

Estructura del proyecto

src/
  index.ts            entrypoint: CLI args, config load, lifecycle
  config.ts           zod schema, validation, defaults
  logger.ts           JSON-lines logger (stderr)
  gree/
    protocol.ts       AES v1/v2 + pack envelope
    commands.ts       field codes, value maps, swing maps
    device.ts         GreeDevice: scan/bind/poll/command state machine
    manager.ts        DeviceManager: registry, resolve, health summary
    types.ts          shared types
  mcp/
    server.ts         McpServer construction
    tools.ts          tool handlers
    resources.ts      per-device resources
  transport/
    stdio.ts          stdio transport
    http.ts           Streamable HTTP + legacy SSE + /healthz
  auth/
    bearer.ts         bearer-token middleware

Fuera del alcance

  • Sin topología de puente GCloud / sub-dispositivo (bridge). El plugin de referencia admite dispositivos detrás de un puente (mac@bridgemac); este servidor está dirigido intencionalmente solo a unidades WiFi direccionables directamente. TODO: agregar descubrimiento de puente/sub-dispositivo y el protocolo de enlace subDev/sublist si es necesario.
  • Sin interfaz web.

Aviso legal

Este es un proyecto independiente y no oficial. No está afiliado, respaldado ni apoyado por GREE Electric Appliances Inc. ni por ninguna de sus subsidiarias. "GREE" y cualquier marca comercial relacionada pertenecen a sus respectivos propietarios y se utilizan aquí solo para describir compatibilidad.

Lo construí en mi tiempo libre y lo mantengo como un proyecto personal de hobby. Se proporciona tal cual, sin ninguna garantía; úsalo bajo tu propio riesgo. Controla hardware real de calefacción/enfriamiento, así que pruébalo con cuidado en tu propio entorno.

GDPR / protección de datos

Esta es una herramienta autoalojada, personal/doméstica. Se ejecuta completamente en tu propia máquina/LAN, no tiene análisis ni servicios de terceros, no realiza llamadas de red externas y no persiste nada en disco (el estado del dispositivo se mantiene en memoria; tu bearerToken y las MAC de los dispositivos viven solo en tu config.json local). Los únicos valores adyacentes a datos personales que maneja son identificadores de dispositivo (MAC y direcciones IP de LAN), que pueden aparecer en los registros.

Si se usa en tu propio hogar, esto generalmente cae bajo la exención de "actividad puramente personal o doméstica" del GDPR (Art. 2(2)(c), Considerando 18), lo que significa que el GDPR generalmente no aplica. Si, en cambio, lo implementas en un contexto donde procesas datos de otras personas (por ejemplo, un lugar de trabajo, una propiedad en alquiler o cualquier entorno comercial), tú eres el responsable del tratamiento y eres el único responsable de tu propio cumplimiento del GDPR, incluida la seguridad del transporte, la retención de registros, la transparencia y cualquier base legal requerida.

Cualquier comentario, escaneo o evaluación de cumplimiento asociado con este proyecto es solo una ayuda preliminar e informativa: no es asesoramiento legal ni sustituye una auditoría legal calificada. Los autores no aceptan responsabilidad por cómo se implementa o utiliza el software.