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 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) yhttp(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
| Bandera | Predeterminado | Descripción |
|---|---|---|
--transport <stdio|http> | stdio | Modo de transporte. |
--config <path> | $GREE_MCP_CONFIG | Ruta al archivo de configuración. Requerido para modo HTTP; opcional para stdio (inicia sin dispositivos si se omite). |
--host <host> | 0.0.0.0 | Host de enlace HTTP (modo http). Anula la configuración. Debe ser no vacío. |
--port <port> | 8080 | Puerto de enlace HTTP (modo http). Anula la configuración. Debe ser un entero 1–65535. |
--log-level <debug|info|warn|error> | info | Verbosidad 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
| Campo | Tipo | Predeterminado | Notas |
|---|---|---|---|
bearerToken | string | — (requerido) | Token requerido en cada solicitud HTTP/SSE. Mínimo 32 caracteres. Ignorado en modo stdio. |
udpPort | number | 7000 | Puerto UDP en el que escuchan los dispositivos. |
updateInterval | number (ms) | 1000 | Intervalo de sondeo de estado predeterminado. |
retryInterval | number (ms) | 5000 | Intervalo de reintento/detección de desconexión predeterminado. |
host | string | 0.0.0.0 | Host de enlace HTTP predeterminado (anulable por --host). |
port | number | 8080 | Puerto de enlace HTTP predeterminado (anulable por --port). |
corsOrigins | string[] | [] | Orígenes CORS permitidos para modo HTTP. Vacío desactiva CORS; ["*"] permite cualquier origen; de lo contrario, una lista de permitidos explícita. |
devices | array | — (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
| Campo | Tipo | Predeterminado | Notas |
|---|---|---|---|
name | string | — (requerido) | Nombre descriptivo; utilizable como alias de selector de herramientas. |
room | string | — | Etiqueta de agrupación opcional. |
address | string 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é. |
mac | string | — (requerido) | 12 caracteres hexadecimales (los separadores/mayúsculas se normalizan). Clave principal para todas las herramientas y el vínculo. |
model | string | — | Solo visualización. |
nameFan | string | — | Nombre de ventilador separado opcional (solo visualización). |
serialNumber | string | — | Opcional; reservado para identidad de caché de claves. |
minimumTargetTemperature | number | 16 | Límite inferior para set_target_temperature. |
maximumTargetTemperature | number | 30 | Límite superior para set_target_temperature. |
oscillation.on/off.{horizontal,vertical} | enum | on=full, off=default | Posiciones de oscilación aplicadas por set_oscillation. Ver enums abajo. |
xFan | boolean | false | Controla la herramienta set_xfan para este dispositivo. |
lightControl | boolean | false | Controla la herramienta set_light para este dispositivo. |
fakeSensor | boolean | false | Si la unidad no tiene sensor real, estima la temperatura actual a partir del objetivo y marca estimated: true. |
sensorOffset | number (°C) | 0 | Calibración añadida a la temperatura actual decodificada. |
speedSteps | 3 | 5 | 5 | Pasos físicos del ventilador; se usa para mapear set_fan_speed hacia abajo en unidades de 3 velocidades. |
encryptionVersion | 0 | 1 | 2 | 0 | 0 = detección automática, 1 = forzar ECB, 2 = forzar GCM. |
updateInterval | number (ms) | hereda del nivel superior | Anulación por dispositivo. |
retryInterval | number (ms) | hereda del nivel superior | Anulación por dispositivo. |
Nota sobre
sensorOffsety la decodificación de temperatura. La mayoría de las unidades GREE reportan el sensor interno (TemSen) comoactual°C + 40. El servidor resta ese desplazamiento base fijo para decodificar, y luego añade tusensorOffsetcomo 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ásswing-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.
| Herramienta | Esquema de entrada | Descripció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
OPTIONSse responde antes de la autenticación (la verificación previa no lleva encabezadoAuthorization). - El encabezado de respuesta
Mcp-Session-Idse 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 hostes la forma más simple de darle eso al contenedor en Linux; de lo contrario, establece eladdressde 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,actionyoutcomedel 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 infopredeterminado;debugregistra 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
stdiono 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
bearerTokeny las MAC de los dispositivos — vive en tuconfig.jsonlocal, que.gitignoreya 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 enlacesubDev/sublistsi 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.