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

Un servidor Model Context Protocol (MCP) que controla aires acondicionados WiFi compatibles con GREE / EWPE directamente a través de su protocolo UDP nativo. Sin Homebridge, sin nube: habla directamente con las unidades en tu LAN.

El protocolo de cable GREE (cifrado AES, flujo de escaneo/vinculación/estado/comando) está implementado desde cero basándose en eibenp/homebridge-gree-airconditioner y tomikaa87/gree-remote.

  • Dos transportes: MCP stdio (para Claude Desktop y otros clientes locales) y http (Streamable HTTP 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), auto-detectados por dispositivo.
  • Sondeo en segundo plano: cada dispositivo está vinculado y sondeado continuamente; las llamadas a herramientas se disparan de inmediato y el siguiente sondeo confirma el nuevo estado.

Requisitos

  • Node.js >= 20
  • Las unidades de aire acondicionado deben estar en la misma LAN/subred que el servidor (UDP broadcast/unicast 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 el TypeScript directamente con npm run dev:stdio / npm run dev:http.

Banderas CLI

BanderaValor por defectoDescripción
--transport <stdio|http>stdioModo de transporte.
--config <path>$GREE_MCP_CONFIGRuta al archivo de configuración (obligatorio).
--host <host>0.0.0.0Host de enlace HTTP (modo http). Anula la configuración.
--port <port>8080Puerto de enlace HTTP (modo http). Anula la configuración.
--log-level <debug|info|warn|error>infoVerbosidad del registro (líneas JSON en stderr).

La ruta al archivo de configuración también se puede proporcionar mediante la variable de entorno GREE_MCP_CONFIG.


Configuración

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

Campos de nivel superior

CampoTipoValor por defectoNotas
bearerTokenstring— (obligatorio)Token requerido en cada petición HTTP/SSE. Se ignora 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 el modo HTTP. Vacío desactiva CORS; ["*"] permite cualquier origen; de lo contrario, una lista explícita de permitidos.
devicesarray— (obligatorio, ≥1)Una entrada por aire acondicionado. Los valores mac duplicados se rechazan.

Campos por dispositivo

CampoTipoValor por defectoNotas
namestring— (obligatorio)Nombre descriptivo; utilizable como alias selector de herramientas.
roomstringEtiqueta de agrupación opcional.
addressIPv4 stringSi se define, el servidor hace unicast a esa dirección. Si se omite, descubre la IP por MAC mediante UDP broadcast y luego la guarda en caché.
macstring— (obligatorio)12 caracteres hex (los separadores/mayúsculas se normalizan). Clave principal para todas las herramientas y la vinculación.
modelstringSolo visualización.
nameFanstringNombre de ventilador separado opcional (solo visualización).
serialNumberstringOpcional; reservado para la identidad de la 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 las enumeraciones más abajo.
xFanbooleanfalseHabilita la herramienta set_xfan para este dispositivo.
lightControlbooleanfalseHabilita 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 que se suma 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 = auto-detección, 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 suma tu sensorOffset como calibración encima. Por lo tanto, currentTemperature = TemSen − 40 + sensorOffset.

Enumeraciones de posición de oscilación

  • Vertical (SwUpDn): default, full, fixed-top, fixed-upper-middle, fixed-middle, fixed-lower-middle, fixed-bottom (además de 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 de 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 el registro de depuración activado 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 el firmware version. Otras opciones:

  • Revisa la lista de clientes DHCP de tu router para obtener la MAC del adaptador WiFi del aire acondicionado (quita los dos puntos y ponla en minúsculas).
  • Usa la app oficial GREE+ / EWPE Smart, o cualquier utilidad de escaneo GREE, que reporte el id de 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_* disparan el comando UDP de inmediato y reportan "command sent"; el bucle de sondeo en segundo plano confirma el nuevo estado poco después. Si un dispositivo está desconectado/sin vincular, 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 el último modo/temperatura conocido.
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. Se rechaza (no se limita) 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 se expone también como un 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 de procesos 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 una cabecera WWW-Authenticate: Bearer. /healthz es sin autenticación.

CORS (clientes de navegador)

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

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

Comprobación de salud

curl http://localhost:8080/healthz
# {"status":"ok","total":2,"bound":1,"unbound":1,"devices":[...]}

Streamable HTTP moderno

Inicialización (observa la cabecera Accept obligatoria y que el id de sesión vuelve en una cabecera 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, por defecto en modo HTTP leyendo /config/config.json.

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 de nivel 2 a la subred de los aires acondicionados. --network host es la forma más sencilla de dárselo al contenedor en Linux; de lo contrario, define el address de cada dispositivo explícitamente y asegura que el enrutamiento UDP/7000 hasta las unidades funcione desde la red del contenedor.

El contenedor EXPOSE 8080. Anula los argumentos del entrypoint 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 el mac del dispositivo, action y outcome.
  • El token bearer y todas las claves AES/dispositivo nunca se registran.
  • Los registros contienen identificadores de dispositivo (mac, dirección IP). Cuando se ejecuta como servicio de larga duración (systemd, Docker, etc.), limita la retención con la rotación de registros habitual 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 pon un proxy reverso con terminación TLS (Caddy, nginx, …) delante; de lo contrario, el token y los datos de las peticiones quedan expuestos en tránsito. El modo stdio no tiene exposición a la red.

Esta es una herramienta autoalojada, personal/doméstica, sin analíticas, sin servicios de terceros y sin persistencia de datos en disco (el estado de los dispositivos 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 la criptografía del protocolo (idasyctas v1/v2 de cifrado-descifrado y un vector de respuesta conocida, además del empaquetado/desempaquetado de sobres) y la validación del esquema de configuración (valores predeterminados, normalización de MAC, herencia de intervalos, rechazo de MAC duplicadas y valores inválidos).

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 de alcance

  • Sin topología de puente GCloud / sub-dispositivos (bridge). El plugin de referencia admite dispositivos detrás de un puente (mac@bridgemac); este servidor apunta intencionalmente solo a unidades WiFi direccionables directamente. TODO: añadir descubrimiento de sub-dispositivos/puente 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í únicamente 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/refrigeración, así que pruébalo con cuidado en tu propio entorno.

GDPR / protección de datos

Esta es una herramienta autohospedada, 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 relacionados con datos personales que maneja son identificadores de dispositivo (MAC y direcciones IP de LAN), que pueden aparecer en los registros.

Cuando se usa en tu propio hogar, esto normalmente cae bajo la exención del GDPR de "actividad puramente personal o doméstica" (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 (p. ej., un lugar de trabajo, una propiedad en alquiler o cualquier entorno comercial), tú eres el responsable del tratamiento de datos 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, análisis 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 cualificada. Los autores no aceptan ninguna responsabilidad por cómo se implementa o utiliza el software.