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

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

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) e http (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

FlagPadrãoDescrição
--transport <stdio|http>stdioModo de transporte.
--config <path>$GREE_MCP_CONFIGCaminho para o arquivo de configuração. Obrigatório no modo HTTP; opcional no stdio (inicia sem dispositivos se omitido).
--host <host>0.0.0.0Host de bind HTTP (modo http). Substitui a configuração. Deve ser não vazio.
--port <port>8080Porta de bind HTTP (modo http). Substitui a configuração. Deve ser um inteiro de 1 a 65535.
--log-level <debug|info|warn|error>infoVerbosidade 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

CampoTipoPadrãoObservações
bearerTokenstring— (obrigatório)Token exigido em toda requisição HTTP/SSE. Mínimo de 32 caracteres. Ignorado no modo stdio.
udpPortnumber7000Porta UDP em que os dispositivos escutam.
updateIntervalnumber (ms)1000Intervalo padrão de poll de status.
retryIntervalnumber (ms)5000Intervalo padrão de retry/detecção de offline.
hoststring0.0.0.0Host de bind HTTP padrão (substituível por --host).
portnumber8080Porta de bind HTTP padrão (substituível por --port).
corsOriginsstring[][]Origens CORS permitidas para o modo HTTP. Vazio desativa CORS; ["*"] permite qualquer origem; caso contrário, uma lista explícita de permissões.
devicesarray— (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

CampoTipoPadrãoObservações
namestring— (obrigatório)Nome amigável; utilizável como alias de seletor de ferramenta.
roomstring—Rótulo de agrupamento opcional.
addressstring IPv4—Se definido, o servidor faz unicast para ele. Se omitido, descobre o IP pelo MAC via broadcast UDP e o armazena em cache.
macstring— (obrigatório)12 caracteres hexadecimais (separadores/maiúsculas são normalizados). Chave primária para todas as ferramentas e vinculação.
modelstring—Apenas exibição.
nameFanstring—Nome opcional separado do ventilador (apenas exibição).
serialNumberstring—Opcional; reservado para identidade de cache de chave.
minimumTargetTemperaturenumber16Limite inferior para set_target_temperature.
maximumTargetTemperaturenumber30Limite superior para set_target_temperature.
oscillation.on/off.{horizontal,vertical}enumon=full, off=defaultPosições de swing aplicadas por set_oscillation. Veja enums abaixo.
xFanbooleanfalseControla a ferramenta set_xfan para este dispositivo.
lightControlbooleanfalseControla a ferramenta set_light para este dispositivo.
fakeSensorbooleanfalseSe a unidade não tiver sensor real, estima a temperatura atual a partir da alvo e sinaliza estimated: true.
sensorOffsetnumber (°C)0Calibração adicionada à temperatura atual decodificada.
speedSteps3 | 55Passos físicos do ventilador; usado para mapear set_fan_speed para baixo em unidades de 3 velocidades.
encryptionVersion0 | 1 | 200 = detecção automática, 1 = forçar ECB, 2 = forçar GCM.
updateIntervalnumber (ms)herda do nível superiorSubstituição por dispositivo.
retryIntervalnumber (ms)herda do nível superiorSubstituição por dispositivo.

Observação sobre sensorOffset e decodificação de temperatura. A maioria das unidades GREE reporta o sensor interno (TemSen) como actual°C + 40. O servidor subtrai esse offset base fixo para decodificar e depois adiciona seu sensorOffset como 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 (mais swing-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.

FerramentaSchema de entradaDescriçã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çalho Authorization).
  • 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 o address de 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, action e outcome do 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; debug registra 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 stdio nã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 bearerToken e os MACs dos dispositivos — fica no seu config.json local, que .gitignore já 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 handshake subDev/sublist se 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.