GREE MCP Server
Servidor MCP local na LAN para controlar condicionadores de ar WiFi GREE / EWPE através do protocolo UDP nativo. Sem nuvem. Transportes stdio + HTTP.
Documentação
gree-ac-mcp-server
Um servidor Model Context Protocol (MCP) que controla ar-condicionados WiFi compatíveis com GREE / EWPE diretamente pelo protocolo UDP nativo. Sem Homebridge, sem nuvem — ele fala diretamente com as unidades na sua rede local.
O protocolo de fio da GREE (criptografia AES, fluxo de scan/bind/status/comando) é implementado do zero com base em eibenp/homebridge-gree-airconditioner e tomikaa87/gree-remote.
- Dois transportes: MCP
stdio(para Claude Desktop e outros clientes locais) ehttp(Streamable HTTP moderno e HTTP+SSE legado), com autenticação bearer obrigatória no HTTP. - Ambos os esquemas de criptografia: v1 (AES-128-ECB) e v2 (AES-128-GCM), detectados automaticamente por dispositivo.
- Polling em segundo plano: cada dispositivo é vinculado e consultado continuamente; chamadas de ferramentas são executadas imediatamente e o próximo poll confirma o novo estado.
Requisitos
- Node.js >= 22
- As unidades de ar-condicionado devem estar na mesma LAN/sub-rede do servidor (broadcast/unicast UDP na porta 7000).
Instalação e build
npm install
npm run build
Início 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 o desenvolvimento, você pode executar o TypeScript diretamente com npm run dev:stdio / npm run dev:http.
Flags de CLI
| Flag | Padrão | Descrição |
|---|---|---|
--transport <stdio|http> | stdio | Modo de transporte. |
--config <path> | $GREE_MCP_CONFIG | Caminho para o arquivo de configuração. Obrigatório no modo HTTP; opcional no stdio (inicia sem dispositivos se omitido). |
--host <host> | 0.0.0.0 | Host de bind HTTP (modo http). Substitui a configuração. Deve ser não vazio. |
--port <port> | 8080 | Porta de bind HTTP (modo http). Substitui a configuração. Deve ser um inteiro de 1 a 65535. |
--log-level <debug|info|warn|error> | info | Verbosidade de log (linhas JSON no stderr). |
O caminho do arquivo de configuração também pode ser fornecido pela variável de ambiente GREE_MCP_CONFIG.
Configuração
Arquivo JSON validado com zod. Em erro de validação, o servidor imprime o campo/dispositivo problemático e sai com código não zero. O servidor deve ser reiniciado para aplicar alterações de configuração (hot-reload não é implementado).
Campos de nível superior
| Campo | Tipo | Padrão | Observações |
|---|---|---|---|
bearerToken | string | — (obrigatório) | Token exigido em toda requisição HTTP/SSE. Mínimo de 32 caracteres. Ignorado no modo stdio. |
udpPort | number | 7000 | Porta UDP em que os dispositivos escutam. |
updateInterval | number (ms) | 1000 | Intervalo padrão de poll de status. |
retryInterval | number (ms) | 5000 | Intervalo padrão de retry/detecção de offline. |
host | string | 0.0.0.0 | Host de bind HTTP padrão (substituível por --host). |
port | number | 8080 | Porta de bind HTTP padrão (substituível por --port). |
corsOrigins | string[] | [] | Origens CORS permitidas para o modo HTTP. Vazio desativa CORS; ["*"] permite qualquer origem; caso contrário, uma lista explícita de permissões. |
devices | array | — (obrigatório, ≥1) | Uma entrada por ar-condicionado. Valores duplicados de mac e de name (insensíveis a maiúsculas/minúsculas) são rejeitados. |
Campos por dispositivo
| Campo | Tipo | Padrão | Observações |
|---|---|---|---|
name | string | — (obrigatório) | Nome amigável; utilizável como alias de seletor de ferramenta. |
room | string | — | Rótulo de agrupamento opcional. |
address | string IPv4 | — | Se definido, o servidor faz unicast para ele. Se omitido, descobre o IP pelo MAC via broadcast UDP e o armazena em cache. |
mac | string | — (obrigatório) | 12 caracteres hexadecimais (separadores/maiúsculas são normalizados). Chave primária para todas as ferramentas e vinculação. |
model | string | — | Apenas exibição. |
nameFan | string | — | Nome opcional separado do ventilador (apenas exibição). |
serialNumber | string | — | Opcional; reservado para identidade de cache de chave. |
minimumTargetTemperature | number | 16 | Limite inferior para set_target_temperature. |
maximumTargetTemperature | number | 30 | Limite superior para set_target_temperature. |
oscillation.on/off.{horizontal,vertical} | enum | on=full, off=default | Posições de swing aplicadas por set_oscillation. Veja enums abaixo. |
xFan | boolean | false | Controla a ferramenta set_xfan para este dispositivo. |
lightControl | boolean | false | Controla a ferramenta set_light para este dispositivo. |
fakeSensor | boolean | false | Se a unidade não tiver sensor real, estima a temperatura atual a partir da alvo e sinaliza estimated: true. |
sensorOffset | number (°C) | 0 | Calibração adicionada à temperatura atual decodificada. |
speedSteps | 3 | 5 | 5 | Passos físicos do ventilador; usado para mapear set_fan_speed para baixo em unidades de 3 velocidades. |
encryptionVersion | 0 | 1 | 2 | 0 | 0 = detecção automática, 1 = forçar ECB, 2 = forçar GCM. |
updateInterval | number (ms) | herda do nível superior | Substituição por dispositivo. |
retryInterval | number (ms) | herda do nível superior | Substituição por dispositivo. |
Observação sobre
sensorOffsete decodificação de temperatura. A maioria das unidades GREE reporta o sensor interno (TemSen) comoactual°C + 40. O servidor subtrai esse offset base fixo para decodificar e depois adiciona seusensorOffsetcomo calibração por cima. Portanto,currentTemperature = TemSen − 40 + sensorOffset.
Enums de posição de swing
- Vertical (
SwUpDn):default,full,fixed-top,fixed-upper-middle,fixed-middle,fixed-lower-middle,fixed-bottom(maisswing-top/swing-upper-middle/swing-middle/swing-lower-middle/swing-bottom). - Horizontal (
SwingLfRig, apenas em unidades com lâminas horizontais):default,full,fixed-left,fixed-center-left,fixed-center,fixed-center-right,fixed-right.
Como obter o MAC de um dispositivo
O MAC é o ID do dispositivo GREE (uma string hexadecimal de 12 caracteres, ex.: 502cc6aabbcc). Seguindo o método
documentado do plugin homebridge, a maneira mais fácil é executar este servidor (ou o plugin homebridge)
com logging de debug na mesma LAN e observar as respostas de scan:
node dist/index.js --transport http --config ./config.json --log-level debug
Cada unidade descoberta registra uma linha device discovered contendo seu mac, address, model
e firmware version. Outras opções:
- Verifique a lista de clientes DHCP do seu roteador para o MAC do adaptador WiFi do ar-condicionado (remova os dois-pontos, use minúsculas).
- Use o aplicativo oficial GREE+ / EWPE Smart, ou qualquer utilitário de scan GREE, que reporta o ID do dispositivo.
Ferramentas MCP
Toda ferramenta aceita um seletor de dispositivo: mac (canônico) ou name (alias, comparado com a
configuração). Forneça um deles. Ferramentas set_* disparam o comando UDP imediatamente e reportam
"command sent"; o loop de poll em segundo plano confirma o novo estado logo em seguida. Se um dispositivo estiver
offline/não vinculado, ferramentas de escrita retornam um erro em vez de suceder silenciosamente.
| Ferramenta | Schema de entrada | Descrição |
|---|---|---|
list_devices | {} | Todos os dispositivos configurados com status online/bound e último modo/temperatura conhecidos. |
get_device_status | { mac?, name? } | Status decodificado completo de um dispositivo. |
get_room_temperature | { mac?, name? } | Temperatura atual calibrada (°C). Sinaliza estimated: true quando derivada via fakeSensor. |
set_power | { mac?, name?, on: boolean } | Liga/desliga a unidade. |
set_mode | { mac?, name?, mode: "auto"|"cool"|"dry"|"fan"|"heat" } | Define o modo (também liga). |
set_target_temperature | { mac?, name?, temperature: number } | Define a temperatura alvo em °C. Rejeitado (não limitado) se estiver fora do intervalo mínimo/máximo do dispositivo. |
set_fan_speed | { mac?, name?, speed: "auto"|"quiet"|"low"|"medium-low"|"medium"|"medium-high"|"high"|"turbo" } | Define a velocidade do ventilador; quiet/turbo ativam modos dedicados; mapeado para baixo em unidades de 3 velocidades. |
set_oscillation | { mac?, name?, on: boolean } | Aplica as posições de swing on/off configuradas. |
set_xfan | { mac?, name?, on: boolean } | X-Fan / sopro. Só utilizável quando xFan: true. |
set_light | { mac?, name?, on: boolean } | Luz do display. Só utilizável quando lightControl: true. |
set_quiet_mode | { mac?, name?, on: boolean } | Modo silencioso (ligar desativa o turbo). |
set_turbo_mode | { mac?, name?, on: boolean } | Modo turbo/potente (ligar desativa o silencioso). |
Cada dispositivo também é exposto como um recurso em gree://device/<mac> retornando seu status
decodificado como JSON.
Uso com Claude Desktop (stdio)
Adicione ao 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"
]
}
}
}
Nenhum token bearer é necessário no modo stdio (o pipe do processo é o limite de confiança).
Uso HTTP
A autenticação bearer é obrigatória em /mcp, /sse e /messages. Tokens ausentes/inválidos recebem
401 com um cabeçalho WWW-Authenticate: Bearer. /healthz é não autenticado e retorna apenas identificadores de dispositivo anonimizados (MAC apenas com prefixo do fabricante, nome apenas com a primeira letra).
CORS (clientes de navegador)
Para clientes MCP baseados em navegador (ex.: MCP Inspector), defina corsOrigins na configuração. O CORS é
desativado por padrão (nenhum cabeçalho adicionado), então clientes que não são de navegador, como Claude Desktop, não são
afetados. Quando ativado:
- A pré-verificação
OPTIONSé respondida antes da autenticação (a pré-verificação não carrega cabeçalhoAuthorization). - O cabeçalho de resposta
Mcp-Session-Idé exposto para que o JS do cliente possa ler o ID da sessão. - A autenticação ainda é aplicada na requisição real; apenas origens listadas recebem um
Access-Control-Allow-Origin.
// config.json
"corsOrigins": ["https://inspector.example.com"] // or ["*"] to allow any origin
Verificação de saúde
curl http://localhost:8080/healthz
# {"status":"ok","total":2,"bound":1,"unbound":1,
# "devices":[{"mac":"502cc6******","name":"T***","bound":true}]}
Streamable HTTP moderno
Inicialize (observe o cabeçalho Accept obrigatório e que o ID da sessão volta em um cabeçalho de resposta):
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>
Depois reutilize esse ID de sessão:
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 legado
# 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
A imagem é multi-estágio e roda como o usuário não root node, com padrão para o modo HTTP lendo
/config/config.json.
Imagens multi-arquitetura pré-construídas (linux/amd64 + linux/arm64) são publicadas no GitHub Container
Registry:
docker run --rm \
--network host \
-v "$(pwd)/config.json:/config/config.json:ro" \
ghcr.io/marcinn2/gree-ac-mcp:latest
Ou construa você mesmo:
docker build -t gree-ac-mcp-server .
docker run --rm \
--network host \
-v "$(pwd)/config.json:/config/config.json:ro" \
gree-ac-mcp-server
A descoberta/broadcast UDP precisa de acesso L2 à sub-rede do ar-condicionado.
--network hosté a maneira mais simples de dar isso ao contêiner no Linux; caso contrário, defina oaddressde cada dispositivo explicitamente e garanta que o roteamento UDP/7000 até as unidades funcione a partir da rede do contêiner.
O contêiner EXPOSEs 8080. Substitua os argumentos do entrypoint para alterar transporte/porta, ex.:
docker run ... gree-ac-mcp-server --transport http --config /config/config.json --port 9000.
Logging e segurança
- Os logs são linhas JSON no stderr (o stdout é reservado para o canal MCP no modo stdio),
incluindo
mac,actioneoutcomedo dispositivo. - O token bearer e todas as chaves AES/do dispositivo nunca são registrados.
- Os logs contêm identificadores de dispositivo (
mac, endereço IP). Ao rodar como um serviço de longa duração (systemd, Docker, etc.), limite a retenção com rotação de log normal para que eles não se acumulem indefinidamente. Mantenha o padrão--log-level info;debugregistra mais identificadores. - O modo HTTP usa autenticação bearer em texto puro. Execute-o apenas em uma LAN doméstica confiável, ou coloque um
proxy reverso com terminação TLS (Caddy, nginx, …) na frente — caso contrário, o token e os
dados da requisição ficam expostos em trânsito. O modo
stdionão tem exposição de rede.
Esta é uma ferramenta auto-hospedada, pessoal/doméstica, sem analytics, sem serviços de terceiros e sem persistência de dados em disco (o estado do dispositivo é mantido apenas em memória). A configuração — incluindo seu
bearerTokene os MACs dos dispositivos — fica no seuconfig.jsonlocal, que.gitignorejá exclui do controle de versão.
Testes
npm test
Cobre a criptografia do protocolo (round-trips de criptografia/descriptografia v1/v2 e um vetor de resposta conhecido, além de empacotamento/desempacotamento de envelope), a validação do schema de configuração (padrões, normalização de MAC, herança de intervalo, rejeição de MAC duplicado/nome duplicado, valor inválido e bearerToken curto), o middleware de autenticação bearer (aceita o token correto; rejeita tokens errados, com comprimento incompatível e malformados) e o middleware CORS (allow-listing de origem, curinga e tratamento de preflight).
Estrutura do projeto
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
Fora do escopo
- Sem topologia de ponte GCloud / sub-dispositivo (bridge). O plugin de referência suporta dispositivos atrás de uma bridge (
mac@bridgemac); este servidor tem como alvo intencional apenas unidades WiFi diretamente endereçáveis. TODO: adicionar descoberta de bridge/sub-dispositivo e o handshakesubDev/sublistse necessário. - Sem interface web.
Aviso legal
Este é um projeto independente e não oficial. Não é afiliado, endossado ou apoiado pela GREE Electric Appliances Inc. ou qualquer uma de suas subsidiárias. "GREE" e quaisquer marcas registradas relacionadas pertencem aos seus respectivos proprietários e são usadas aqui apenas para descrever compatibilidade.
Construí isto no meu tempo livre e mantenho como um projeto pessoal de hobby. É fornecido como está, sem qualquer garantia; use por sua conta e risco. Ele controla hardware real de aquecimento/resfriamento, então teste com cuidado no seu próprio ambiente.
GDPR / proteção de dados
Esta é uma ferramenta auto-hospedada, pessoal/doméstica. Ela roda inteiramente na sua própria máquina/LAN, não tem analytics ou serviços de terceiros, não faz chamadas de rede externas e não persiste nada em disco (o estado do dispositivo é mantido em memória; seu bearerToken e os MACs dos dispositivos vivem apenas no seu config.json local). Os únicos valores adjacentes a dados pessoais que ela manipula são identificadores de dispositivo (MAC e endereços IP da LAN), que podem aparecer em logs.
Usado na sua própria casa, isso normalmente se enquadra na isenção de "atividade puramente pessoal ou doméstica" do GDPR (Art. 2(2)(c), Considerando 18), o que significa que o GDPR geralmente não se aplica. Se você, em vez disso, implantar em um contexto onde processa dados de outras pessoas (por exemplo, local de trabalho, propriedade alugada ou qualquer ambiente comercial), você é o controlador de dados e é o único responsável pela sua própria conformidade com o GDPR, incluindo segurança de transporte, retenção de logs, transparência e qualquer base legal necessária.
Qualquer comentário, varredura ou avaliação de conformidade associada a este projeto é um auxílio preliminar e informativo apenas — não é aconselhamento jurídico e não substitui uma auditoria jurídica qualificada. Os autores não aceitam responsabilidade por como o software é implantado ou usado.