Skip to main content
Glama
sandraschi

Dreame D20 Pro Plus MCP Server

README.md
# Dreame D20 Pro Plus MCP Server

<p align="center">
  <a href="https://github.com/casey/just"><img src="https://img.shields.io/badge/just-ready_to_go-7c5cfc?style=flat-square&logo=just&logoColor=white" alt="Just"></a>
  <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
  <a href="https://python.org"><img src="https://img.shields.io/badge/Python-3.13+-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python"></a>
  <a href="https://biomejs.dev"><img src="https://img.shields.io/badge/Linted_with-Biome-60a5fa?style=flat-square&logo=biome&logoColor=white" alt="Biome"></a>
  <a href="https://github.com/PrefectHQ/fastmcp"><img src="https://img.shields.io/badge/FastMCP-3.2-7c5cfc?style=flat-square" alt="FastMCP"></a>
</p>


> 📖 **[Installation Guide](INSTALL.md)** — quick start, manual setup, and troubleshooting

FastMCP 3.2.0 MCP server and webapp for the **Dreame D20 Pro Plus** robot vacuum. Uses the **DreameHome cloud API**; local **miio** is optional (hybrid mode: see below).
Protocol layer extracted from [Tasshack/dreame-vacuum](https://github.com/Tasshack/dreame-vacuum).

## Features

- **MCP tools**: `dreame_tool(operation=...)`  status, map, start_clean, stop, pause, go_home, find_robot, battery
- **MCP tools**: `dreame_help(category)`, `dreame_agentic_workflow(goal)` with SEP-1577 sampling, `dreame_shutdown()`, `show_dreame_status_app()` (Prefab card)
- **Prompts**: `dreame_quick_start`, `dreame_diagnostics`
- **Skills**: `skill://dreame-operator/SKILL.md` (also `GET /api/skills`)
- **Resources**: `dreame://status`
- **REST API**: GET /api/v1/health, /api/v1/status, /api/v1/map (+ png/pgm/yaml), /api/v1/diagnostics; POST /api/v1/control/{cmd}, /api/v1/shutdown
- **Webapp**: Dashboard, LIDAR Map, Status, Controls, Settings, Help, MCP Tools, Logs

## Ports

- Backend: **10894** (REST + MCP SSE)
- Dashboard: **10895** (Vite dev server)

## Prerequisites

1. Clone **this** repo and enter it:
   ```powershell
   git clone https://github.com/sandraschi/dreame-mcp.git
   Set-Location dreame-mcp
   ```

2. Clone the Tasshack dreame-vacuum reference repo (protocol + map layer). Default ref path is `D:/Dev/repos/tasshack_dreame_vacuum_ref`:
   ```powershell
   Set-Location D:\Dev\repos
   git clone https://github.com/Tasshack/dreame-vacuum tasshack_dreame_vacuum_ref
   Set-Location dreame-mcp
   ```
   (Adjust paths if your dev folder is not `D:\Dev\repos`; set `DREAME_REF_PATH` accordingly.)

3. Install Python deps from the **dreame-mcp** repository root:
   ```powershell
   uv sync
   ```

## Environment variables

| Variable | Required | Description |
|---|---|---|
| `DREAME_USER` |  | DreameHome email or phone |
| `DREAME_PASSWORD` |  | DreameHome password |
| `DREAME_COUNTRY` |  | Cloud region, default `eu` |
| `DREAME_DID` |  | Device ID, auto-discovered if single device |
| `DREAME_AUTH_KEY` |  | Refresh token from previous login (speeds up startup) |
| `DREAME_REF_PATH` |  | Path to tasshack ref clone (default: `D:/Dev/repos/tasshack_dreame_vacuum_ref`) |
| `DREAME_MCP_PORT` |  | Backend port (default: `10894`) |
| `DREAME_IP` / `DREAME_TOKEN` |  | Local miio (null token if token empty); hybrid with cloud for maps |

## Development and tests

- **Python** (repo root): `uv run ruff check src tests`, `uv run pytest` (CI sets `PYTHONPATH=src`; on Windows: `$env:PYTHONPATH = 'src'; uv run pytest tests`).
- **MCP tool `dreame(operation=...)`** returns **Markdown** for LLM context. For **structured dicts** (same shapes as the REST handlers), import `fetch_status_data`, `fetch_map_data`, and `execute_control_data` from `dreame_mcp.portmanteau` (see `tests/test_map.py`).
- **Live tests** (real robot + cloud): `DREAME_LIVE=1 uv run pytest tests --live` or `--live` flag.
- **Webapp** (`webapp/`): `npm ci` then `npm run biome:ci` and `npm run build`.

## Connection Modes

This server supports three operational modes depending on your `.env` configuration:

| Mode | Credentials | Commands | Lidar Map | Notes |
| :--- | :--- | :---: | :---: | :--- |
| **Local** | `DREAME_IP` | ⚡ Local | ❌ No | Uses the **Null Token trick** (`000...000`) |
| **Cloud** | `USER` + `PWD` | ☁️ Cloud | ✅ Yes | Subject to cloud latency and API rate limits |
| **Hybrid** | **Both** | ⚡ **Local** | ✅ **Yes** | **Recommended**: Fast control + Full visual map |

### The "Null Token" Trick (Bypass)

For users avoiding the DreameHome cloud for controls, you do not need to extract a secret 32-character token. By providing only the `DREAME_IP`, the backend automatically uses a **Null Token** (`32 zeros`). This works on many bridged or circumvention-ready firmwares (like those used with the Tasshack protocol).

## Quick Start

```powershell
git clone https://github.com/sandraschi/dreame-mcp
cd dreame-mcp
just
```

This opens an interactive dashboard showing all available commands. Run `just bootstrap` to install dependencies, then `just serve` to start the full stack, or `just test` for the mocked suite.

### Manual Setup

If you don't have `just` installed:


## Setup

1. **Configure credentials**: Copy `.env.example` to `.env` and fill in your details.
   ```powershell
   # Typical Hybrid Setup (.env)
   DREAME_IP=192.168.0.178
   DREAME_USER=your@email.com
   DREAME_PASSWORD=yourpassword
   ```

2. **Start the system**:
   ```powershell
   # Start backend + webapp together
   .\webapp\start.ps1
   ```

## MCP client config

```json
{
  "mcpServers": {
    "dreame": {
      "url": "http://localhost:10894/sse",
      "transport": "sse"
    }
  }
}
```

## Map (LIDAR / floor plan)

**Map data does not require miIO** on this server: it uses DreameHome cloud + the Tasshack ref at `DREAME_REF_PATH`. Local miIO is optional and often unavailable on DreameHome-only firmware.

- **REST:** `GET http://localhost:10894/api/v1/map` (same JSON as MCP `dreame_tool(operation='map')`).
- **Fields:** `image` (base64-encoded image when decode/render works), `raw_b64` (always on successful cloud fetch; use for custom decoders or **robotics-mcp / yahboom-mcp**), optional `map_data`, optional `render_error` if the PNG path failed.
- **Dashboard:** **Map** page at `http://localhost:10895` shows the image when `image` is present.

**Download path (cloud):** matches Home Assistant’s Tasshack integration — resolve **`OBJECT_NAME`** (property 6.3) when available, then **`get_interim_file_url` / `get_file`** (signed object storage). **`get_device_file`** is only a fallback; it often returns `80001` if the cloud cannot reach the device at that moment.

**Render path:** raw bytes are decoded with **`DreameVacuumMapDecoder.decode_map`** and drawn with **`DreameVacuumMapRenderer.render_map`** (not `DreameMapVacuumMapManager` methods, which only orchestrate HA state). The ref clone’s `custom_components.…` packages are given proper `__path__` at load time so `map.py` imports cleanly.

See **[docs/MAP_AND_ROBOTICS.md](docs/MAP_AND_ROBOTICS.md)** for fleet integration, the JSON contract, and operations.

### Map rendering (dependencies)

The rendered image requires the Tasshack stack: `py-mini-racer`, `numpy`, `Pillow`, `cryptography`, and related pins from `uv.lock`. If `dreame_tool(operation='map')` has `render_error` but `raw_b64` is set, the fetch worked and only decode/render failed; check logs and dependencies.

## Troubleshooting: `Unable to discover the device` / status 502

`GET /api/v1/health` includes **`local_miot`**: `true` only after a successful UDP miio handshake to `DREAME_IP` (port 54321). If you see **`Unable to discover the device` `192.168.x.x`** in logs or **`local_miot: false`**, the robot did not answer the standard miio discovery on the LAN. Typical causes: **DreameHome-only firmware** (no or limited LAN miio), **wrong IP**, **null token not accepted** (add a real **`DREAME_TOKEN`**), or **cloud login failed** (fix **`DREAME_USER` / `DREAME_PASSWORD` / `DREAME_COUNTRY`**, captcha, 2FA) so you get **`DREAME_DID`** and maps. Set **`DREAME_DID`** manually in `.env` when you know it from the app or cloud.

## Docs

- [The Dreame Robo Hoover Saga](docs/ROBO_HOOVER_SAGA.md)  how control worked, broke, and was restored (with the map!)
- [Configuration](docs/CONFIGURATION.md)  env vars, connection modes, ports
- [Development](docs/DEVELOPMENT.md)  stack, recipes, five-gate verification
- [Tools & API](docs/TOOLS.md)  MCP tools, prompts, REST endpoints
- [Troubleshooting](docs/TROUBLESHOOTING.md)  common failures and fixes
- [Onboarding](docs/ONBOARDING.md)  first-run setup (DreameHome account)
- [Map and fleet robotics](docs/MAP_AND_ROBOTICS.md)  HTTP/MCP consumption, miIO vs cloud, yahboom / robotics integration
- [PRD](docs/PRD.md)  product context, ports, **5 Map API contract**
- [Token and Home Assistant](docs/TOKEN_AND_HOME_ASSISTANT.md)  historical miIO reference (v0.2+ uses cloud)

## 🛡️ Industrial Quality Stack

This project adheres to **SOTA** industrial standards for high-fidelity agentic orchestration:

- **Python (Core)**: [Ruff](https://astral.sh/ruff) for linting and formatting. Zero-tolerance for `print` statements in core handlers (`T20`).
- **Python (Types)**: [Pyright](https://github.com/microsoft/pyright) static type checking (five-gate CI).
- **Webapp (UI)**: [Biome](https://biomejs.dev/) for sub-millisecond linting. Strict `noConsoleLog` enforcement.
- **Protocol Compliance**: Hardened `stdout/stderr` isolation to ensure crash-resistant JSON-RPC communication.
- **Automation**: [Justfile](./justfile) recipes for all fleet operations (`just lint`, `just fix`, `just serve`).
- **Security**: Automated audits via `bandit` and `safety`.