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) yhttp(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
| Bandera | Valor por defecto | Descripción |
|---|---|---|
--transport <stdio|http> | stdio | Modo de transporte. |
--config <path> | $GREE_MCP_CONFIG | Ruta al archivo de configuración (obligatorio). |
--host <host> | 0.0.0.0 | Host de enlace HTTP (modo http). Anula la configuración. |
--port <port> | 8080 | Puerto de enlace HTTP (modo http). Anula la configuración. |
--log-level <debug|info|warn|error> | info | Verbosidad 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
| Campo | Tipo | Valor por defecto | Notas |
|---|---|---|---|
bearerToken | string | — (obligatorio) | Token requerido en cada petición HTTP/SSE. Se ignora 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 el modo HTTP. Vacío desactiva CORS; ["*"] permite cualquier origen; de lo contrario, una lista explícita de permitidos. |
devices | array | — (obligatorio, ≥1) | Una entrada por aire acondicionado. Los valores mac duplicados se rechazan. |
Campos por dispositivo
| Campo | Tipo | Valor por defecto | Notas |
|---|---|---|---|
name | string | — (obligatorio) | Nombre descriptivo; utilizable como alias selector de herramientas. |
room | string | — | Etiqueta de agrupación opcional. |
address | IPv4 string | — | Si 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é. |
mac | string | — (obligatorio) | 12 caracteres hex (los separadores/mayúsculas se normalizan). Clave principal para todas las herramientas y la vinculación. |
model | string | — | Solo visualización. |
nameFan | string | — | Nombre de ventilador separado opcional (solo visualización). |
serialNumber | string | — | Opcional; reservado para la identidad de la 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 las enumeraciones más abajo. |
xFan | boolean | false | Habilita la herramienta set_xfan para este dispositivo. |
lightControl | boolean | false | Habilita 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 que se suma 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 = auto-detección, 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 suma tusensorOffsetcomo 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 deswing-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.
| Herramienta | Esquema de entrada | Descripció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
OPTIONSse responde antes de la autenticación (el preflight no lleva cabeceraAuthorization). - Se expone la cabecera de respuesta
Mcp-Session-Idpara 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 hostes la forma más sencilla de dárselo al contenedor en Linux; de lo contrario, define eladdressde 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
macdel dispositivo,actionyoutcome. - 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 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 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
stdiono 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
bearerTokeny las MAC de los dispositivos — vive en tuconfig.jsonlocal, que.gitignoreya 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 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í ú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.