A standalone Model Context Protocol server for the eWeLink CUBE (iHost) smart-home gateway. It exposes every Cube function — pairing, gateway info, device control, live events and sensor history — as MCP tools over Streamable HTTP, so any AI assistant or coder agent can operate your house. It also serves a small live dashboard web page.
Fully independent: the Cube client, types, SSE handling and history database are re-implemented here. Zero dependency on the dashboard repo.
- Pairing — register against the Cube box via the physical link button
- Gateway — info, runtime stats, volume, mute/unmute
- Devices — list, inspect, rename, power, brightness, color temperature, RGB, generic state control, capability queries (e.g. power consumption)
- Live — one persistent SSE connection to the box with auto-reconnect, event buffer and live device cache
- History — own SQLite database, auto-records sensor snapshots every 5 min;
queryable per device/metric over
1h…all - Dashboard —
GET /dashboard: climate charts with date picker, live event feed
- Node.js 20+ and npm
- An eWeLink CUBE / iHost gateway on the same LAN (this was built against
firmware 2.13.2 at
http://(you local IP of the Cupe box or software))
git clone <this-repo> && cd ewelink-cube-mcp
cp .env.example .env
npm installEdit .env:
CUBE_BASE_URL=http://192.168.50.22 # your Cube's address, no trailing slash
CUBE_ACCESS_TOKEN= # leave blank until paired (see below)
CUBE_APP_NAME=ewelink-mcp
MCP_PORT=3001
MCP_API_KEY= # optional: clients must send Authorization: Bearer <key>
HISTORY_DB_PATH=./mcp-history.db
HISTORY_AUTO_RECORD_MS=300000 # snapshot interval, 0 = disabledStart it:
npm run dev # development (tsx)
# or
npm run build && npm startEndpoints:
| URL | What |
|---|---|
http://localhost:3001/mcp |
MCP Streamable HTTP endpoint (for AI clients) |
http://localhost:3001/dashboard |
Live dashboard web page |
http://localhost:3001/health |
Health + SSE status |
Docker:
docker build -t ewelink-cube-mcp .
docker run -p 3001:3001 --env-file .env ewelink-cube-mcpThe Cube issues tokens only after a physical confirmation:
- Call the
cube_pair_waittool (default 30 s window). - Within the window, press the link button on the iHost (a confirmation
pop-up may also appear in the iHost web console at
http://ihost.local). - The tool returns
{"token": "..."}and activates it immediately. - Persist it: set
CUBE_ACCESS_TOKEN=<token>in.env(survives restarts; the token stays valid until the gateway is factory-reset).
Single attempt instead of polling: cube_pair_once.
Check state any time: cube_token_status.
Every tool speaks plain JSON. Conventions used across all signatures:
- Device identity —
serialNumber(the Cubeserial_numberUUID, e.g."05a98571-6f4b-4404-b6a2-3d24be50ed62"). Usecube_list_devicesto discover them. - State object —
statemirrors the Cube capability model:{ "<capability>": { "<field>": value } }. Examples:- power:
{ "power": { "powerState": "on" } } - multi-gang:
{ "toggle": { "toggleState": "off" } } - brightness:
{ "brightness": { "brightness": 80 } }(1–100) - color temp:
{ "color-temperature": { "colorTemperature": 50 } }(0–100) - color:
{ "color-rgb": { "red": 255, "green": 0, "blue": 255 } }
- power:
- Success —
{ "error": 0, "data": {...}, "message": "success" } - Failure —
{ "error": <code>, "data": {}, "message": "<reason>" }, e.g.110005= device offline,110021= capability not queryable on that device. - History ranges —
1h | 6h | 24h | 7d | 30d | 90d | 1y | all. - Metric names —
temperature (°C),humidity (%),battery (%),rssi (dBm),voltage (V),electric-power (W).
| Tool | Signature | Description |
|---|---|---|
cube_token_status |
() |
Whether base URL + token are configured, plus SSE status |
cube_pair_once |
() |
One token request (press link button first) |
cube_pair_wait |
({ timeoutMs?: 5000–120000 = 30000 }) |
Poll until the button press registers; activates token |
| Tool | Signature | Description |
|---|---|---|
cube_gateway_info |
() |
Bridge info: ip, mac, firmware, name |
cube_gateway_runtime |
() |
cpu %, ram %, cpu temp, uptime, SD usage |
cube_gateway_volume |
({ volume: 0–100 }) |
Speaker volume |
cube_gateway_mute / cube_gateway_unmute |
() |
Mute / unmute speaker |
| Tool | Signature | Description |
|---|---|---|
cube_list_devices |
({ category?: string, onlineOnly?: false }) |
All Zigbee devices; filter by display_category |
cube_get_device |
({ serialNumber }) |
Full detail incl. capabilities + current state |
cube_set_device_state |
({ serialNumber, state }) |
Generic control, any capability payload |
cube_power |
({ serialNumber, action: "on" | "off" | "toggle" }) |
Power via power or toggle capability, auto-detected |
cube_set_brightness |
({ serialNumber, brightness: 1–100 }) |
Light brightness |
cube_set_color_temp |
({ serialNumber, colorTemperature: 0–100 }) |
0 warm … 100 cool |
cube_set_rgb |
({ serialNumber, red, green, blue: 0–255 }) |
RGB color |
cube_rename_device |
({ serialNumber, name }) |
Rename |
cube_query_state |
({ serialNumber, capability, query_state }) |
Live capability query, e.g. power-consumption with { type: "summarize", timeRange: { start, end } } |
| Tool | Signature | Description |
|---|---|---|
cube_events_status |
() |
Persistent SSE connection state |
cube_events_recent |
({ limit: 1–200 = 50 }) |
Recent box events (state, online, add, delete) |
cube_live_cache |
() |
Device state cache built from SSE + list seeding |
| Tool | Signature | Description |
|---|---|---|
history_record_snapshot |
({ serialNumber }) |
Record one device now |
history_record_all |
() |
Record all online devices now |
history_query |
({ serialNumber, capability, range = "24h", limit = 2000 (max 20000) }) |
Readings series |
history_devices |
() |
Devices with history + their metrics |
history_latest |
() |
Latest reading per device/metric |
history_stats |
() |
Total reading count |
ewelink://gateway/info— gateway info JSONewelink://devices/list— full device list JSONewelink://events/recent— recent live events JSON
Besides MCP, the server exposes plain REST (used by the dashboard):
GET /dashboard— climate charts (device picker, metric, presets + custom from/to date picker), live event feed. No build step, no external assets.GET /api/gateway,GET /api/devices,GET /api/events?limit=,GET /api/history/devicesGET /api/history?serial=&capability=&range=&limit=or&from=&to=(ISO datetime or epoch ms)POST /api/power{ serialNumber, action }
Each chart line is one device (colors in the legend); X = time in browser timezone, Y = metric value. Points are 5-minute snapshots — gaps mean no recording. Metrics: Temperature °C (air temp), Humidity % (relative), Battery % (wireless devices), Signal dBm (nearer 0 = stronger; −50 good, −80 weak), Power W (current draw), Voltage V. "no data" = no readings for that device/metric/period. The same explanation is shown on the dashboard itself under "Reading the graphs".
Any Streamable-HTTP MCP client works. If MCP_API_KEY is set, send
Authorization: Bearer <key>; clients must also send
Accept: application/json, text/event-stream.
Once connected, just ask — the assistant can list devices, read sensors, switch lights and pull history through the tools above:
src/
index.ts # Express + MCP transport + background jobs
config.ts # env handling
cube/client.ts # Open API v2 client (fetch)
cube/types.ts # Cube type declarations
sse/manager.ts # long-lived SSE: reconnect, event buffer, live cache
db/history.ts # SQLite history layer
mcp/buildServer.ts# all 26 tools + 3 resources
http/api.ts # REST endpoints
http/dashboardPage.ts # dashboard HTML (self-contained)
link button not pressed(401) — press the iHost button, then retry within the window.110005 Device Offline— the end device is unreachable; commands to it fail until it rejoins.110021oncube_query_state— that capability isn't queryable on that device (temperature/humidity are report-only; onlypower-consumptionhas history, and only on devices that declare the capability).- History gaps — the box exposes no sensor-history API (its UI graphs come from internal storage). History exists only for periods this server (or the dashboard app) was running and recording.
Session not found(-32001) — the MCP client must completeinitializefirst and sendMcp-Session-Id+Mcp-Protocol-Versionheaders afterwards.



{ "$schema": "https://opencode.ai/config.json", "mcp": { "servers": { "ewelink": { "type": "remote", "url": "http://localhost:3001/mcp", "oauth": false } } } }