Skip to main content
Glama
roomba-mcp
by auss

roomba-mcp

An MCP server for a Roomba Combo j9+ (stingray, sku c975840). It controls and observes the robot over the local LSS protocol and falls back to the iRobot cloud when the robot is off the LAN.

Tested against firmware stingray+24.29.5. Not affiliated with iRobot.

Scope

In scope:

  • mission control: start, pause, resume, stop, dock, find

  • live state, settings, schedule, mission history

  • consumables, smart maps and regions

Out of scope, deliberately:

  • OTA and firmware endpoints

  • account, team, household transfer and notification endpoints

  • image upload approvals

  • the account reset endpoint

  • manual driving

Related MCP server: Dreame D20 Pro Plus MCP Server

Safety model

  • Reads need no flag.

  • Every command needs ROOMBA_ALLOW_WRITES=1 in the server environment.

  • raw_publish also needs ROOMBA_ALLOW_RAW=1 and the local transport.

  • Region, pass, suction and edge arguments to start_clean need ROOMBA_EXPERIMENTAL=1, because the immediate command shape is unverified on this firmware.

  • There are no confirmation arguments. The gate is the environment, not a question a model can answer.

  • Every command is logged as one JSON line with the tool, payload, transport and result.

  • A write that times out is reported as outcome: "unknown" and is never replayed over the other transport.

Requirements

  • Python 3.12 or newer, or Docker

  • uv for the source install

  • The robot on the same LAN for local control

  • An iRobot account for the first login and for cloud reads

Install

git clone https://github.com/auss/roomba-mcp.git
cd roomba-mcp
uv sync
uv run roomba-mcp version

Login

login is the only command that writes the login cache. The server never logs in by itself.

uv run roomba-mcp login

The command prompts for the account email and password, discovers the current deployment, and writes ~/.config/roomba-mcp/login.json with mode 0600. Pass --blid when the account owns several robots.

The login cache holds the robot password, the local MQTT credentials and a temporary AWS session. When the session expires, cloud reads return login_expired and you run login again.

Configuration

Two files in ~/.config/roomba-mcp (or $XDG_CONFIG_HOME/roomba-mcp):

file

mode

purpose

config.toml

0644

non-secret settings

login.json

0600

account session and robot password

http-token

0600

bearer token for a non-loopback bind

[robot]
blid = "0123456789ABCDEF0123456789ABCDEF"
host = "192.168.1.27"
port = 8883

[http]
host = "0.0.0.0"
port = 8785

[transport]
mode = "auto"

[logging]
level = "info"

Precedence is defaults, then config.toml, then the environment, then command line flags.

Environment variables:

variable

default

meaning

ROOMBA_CONFIG_DIR

~/.config/roomba-mcp

config directory

ROOMBA_CONFIG

<dir>/config.toml

explicit config file

ROOMBA_BLID

from login cache

robot id

ROOMBA_HOST

irobot-<blid>.local

robot host or IP

ROOMBA_ROBOT_PORT

8883

local MQTT port

ROOMBA_TRANSPORT

auto

auto, local or cloud

ROOMBA_HTTP_HOST

127.0.0.1

HTTP bind address

ROOMBA_HTTP_PORT

8785

HTTP port

ROOMBA_HTTP_TOKEN

from http-token

bearer token

ROOMBA_LOG_LEVEL

info

log level

ROOMBA_LOG_FILE

none

extra log file

ROOMBA_ALLOW_WRITES

off

enable commands

ROOMBA_ALLOW_RAW

off

enable raw_publish

ROOMBA_EXPERIMENTAL

off

enable unverified parameters

Safety flags are read from the environment only. They are not accepted in config.toml, so a copied config cannot turn writes on by accident.

Run

HTTP is the primary transport:

uv run roomba-mcp serve --http-host 0.0.0.0 --http-port 8785

A non-loopback bind refuses to start without a token. Create one once:

uv run roomba-mcp token

The token is printed and written to http-token with mode 0600. Rotate it with roomba-mcp token --force and restart the server.

Check the server:

uv run roomba-mcp healthcheck
curl -H "Authorization: Bearer <token>" http://192.168.1.25:8785/healthz

stdio is available for local clients:

uv run roomba-mcp serve --stdio

Client configuration

HTTP:

{
  "mcpServers": {
    "roomba": {
      "type": "http",
      "url": "http://192.168.1.25:8785/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

stdio:

{
  "mcpServers": {
    "roomba": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/roomba-mcp", "roomba-mcp", "serve", "--stdio"]
    }
  }
}

Docker

Run login and token on the host first, then start the container. The config directory is mounted read-only: the container never writes it.

uv run roomba-mcp login
uv run roomba-mcp token
ROOMBA_HOST=192.168.1.27 docker compose up -d --build
curl http://127.0.0.1:8785/healthz

compose.yaml uses host networking, which keeps local discovery simple. compose.bridge.yaml publishes port 8785 instead:

ROOMBA_HOST=192.168.1.27 docker compose -f compose.bridge.yaml up -d --build

Both files run the container as your host uid so the 0600 login cache is readable. If your ids are not 1000, set ROOMBA_UID and ROOMBA_GID.

Set ROOMBA_HOST to the robot IP when mDNS does not resolve inside the container.

Tools

tool

kind

needs

notes

get_status

read

battery, phase, bin, tank, pad, position

get_robot_info

read

identity, transport, capability tiers, flags

get_settings

read

suction, carpet boost, edge clean, pad wetness

get_schedule

read

schedule entries and per-region parameters

get_mission_history

read

login

cloud REST, 1 to 100 missions

get_consumables

read

login

cloud REST, part counters

get_map

read

login

cloud REST, smart maps and regions

find_robot

write

writes

locating sound

start_clean

write

writes

plain start by default

pause_clean

write

writes

resume_clean

write

writes

stop_clean

write

writes

dock

write

writes

set_setting

write

writes

typed subset only

set_schedule

write

writes

replaces the schedule

reset_consumable

write

writes, login

cloud REST

raw_publish

raw

raw, local

debug topics only

start_clean accepts regions, passes, suction and edge_clean only with ROOMBA_EXPERIMENTAL=1. Region ids come from get_map. A region clean sends the same shape the schedule shadow stores.

Resources

URI

contents

roomba://status

live status

roomba://mission

current mission and timeline

roomba://settings

current settings

roomba://schedule

stored schedule

The server publishes notifications/resources/updated through the MCP subscriptions/listen stream. Notifications are debounced to at most one per URI per second, deduplicated by payload hash, and never fired for the initial state. Reads carry stale and age_s so a client can tell cached data from live data.

Transports

mode

behaviour

auto

probe local every 60 s, prefer local, fall back to cloud

local

local LSS only

cloud

AWS IoT MQTT only

The local connection uses TLS with the iRobot certificate, the legacy AES128-SHA256 cipher at @SECLEVEL=0, and SSL_OP_LEGACY_SERVER_CONNECT. The cloud connection uses the AWS IoT WebSocket endpoint with the authorizer headers from the login cache. One connection is alive at a time.

Cloud reads use named shadows: ro-currentstate for live state, rw-settings for settings and rw-schedule for the schedule. The classic cloud shadow only carries identity and capability fields.

Capability tiers

get_robot_info reports a tier per capability:

  • verified: observed working on this robot and firmware.

  • derived-low-risk: reverse engineered from the app, reversible, still behind the writes flag.

  • unverified-physical: unproven wire format, hard error without ROOMBA_EXPERIMENTAL=1.

Development

uv run ruff check src tests
uv run mypy src
uv run pytest

pre-commit runs ruff, an em-dash check and gitleaks on every commit. Install it once:

uv run pre-commit install

CI runs lint, types, unit tests, CodeQL, dependency review, a secret scan and the em-dash check. It never talks to a robot.

See CONTRIBUTING.md for contributions and SECURITY.md for private vulnerability reports and safe deployment.

Tests use sanitized captures from the real robot. See tests/fixtures/README.md.

Design notes live in docs/adr/. Protocol references are vendored in docs/protocol/ with provenance headers.

License

MIT. See LICENSE.

Available Tools

17 tools
dockA

Send the robot back to its dock. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: no named parameters. Returns: result

find_robotA

Make the robot play its locating sound. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: no named parameters. Returns: result

get_consumablesA

Consumable counters: brush, filter, pads and other parts with the remaining life and whether a reset is due.

Inputs: no named parameters. Returns: result

get_mapC

Smart maps with their regions, for use with start_clean.

Inputs: refresh. Returns: result

get_mission_historyB

Recent finished missions from the iRobot cloud (1 to 100).

Inputs: limit. Returns: result

get_robot_infoA

Identity, active transport, login state, capability tiers and the current safety flags. Call this first to see what is allowed.

Inputs: no named parameters. Returns: result

get_scheduleC

The stored cleaning schedule, including per-region parameters.

Inputs: refresh. Returns: result

get_settingsC

Current cleaning settings: suction, carpet boost, edge clean, pad wetness, multi pass, bin pause and child lock.

Inputs: refresh. Returns: result

get_statusA

Live robot state: battery, mission phase, bin, tank, pad, position and the raw reported shadow. Set refresh=true to bypass the cache.

Inputs: refresh. Returns: result

pause_cleanA

Pause the current mission. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: no named parameters. Returns: result

raw_publishA

Debug escape hatch: publish a raw JSON payload to one local topic (cmd, delta, shadow, timeline, testcmd). Needs ROOMBA_ALLOW_RAW=1 and the local transport.

Inputs: topic (required), payload (required). Returns: result

reset_consumableA

Reset one consumable counter by part id from get_consumables. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: part (required). Returns: result

resume_cleanA

Resume a paused mission. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: no named parameters. Returns: result

set_scheduleA

Replace the cleaning schedule. Each entry needs a start object (day, hour, min) and a cmdStr string. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: schedule (required). Returns: result

set_settingA

Change one setting: suction (normal|high), carpet_boost, edge_clean, multi_pass, bin_pause, child_lock (true|false), pad_wetness (1-3). Needs ROOMBA_ALLOW_WRITES=1.

Inputs: name (required), value (required). Returns: result

start_cleanB

Start a whole-home clean. Region, pass, suction and edge arguments need ROOMBA_EXPERIMENTAL=1 and a map from get_map. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: passes, regions, suction, edge_clean. Returns: result

stop_cleanA

Stop the current mission. Needs ROOMBA_ALLOW_WRITES=1.

Inputs: no named parameters. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 17 tools

    Disambiguation4/5

    The set cleanly separates read tools (get_status, get_settings, get_map, get_consumables) from mission control (start/pause/resume/stop_clean) and config writes (set_setting, set_schedule, reset_consumable). Minor overlap exists between get_status and get_robot_info (both report robot state/capabilities), but the descriptions differentiate live state from identity/safety flags.

    Naming Consistency4/5

    Most tools follow a predictable verb_noun pattern (get_status, start_clean, set_schedule, reset_consumable), with a consistent get_ prefix for reads. The lone bare verb 'dock' and the odd 'raw_publish' are small deviations but the overall pattern is readable and predictable.

    Tool Count4/5

    17 tools is on the heavier side but each maps to a genuine robot capability area (status, settings, schedule, maps, consumables, mission lifecycle, raw debug). Nothing appears duplicative, though the debug escape hatch is arguably extra surface.

    Completeness4/5

    The surface covers the full cleaning lifecycle, read-only telemetry, schedules, maps, settings, and consumable resets — a solid end-to-end picture. Gaps are minor: no map editing/region management beyond reading, and no targeted action like emptying the bin, but core workflows are reachable.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables control of Tuvio/Tuya robot vacuum cleaners via a local hub, supporting commands such as start, pause, home, set mode, suction, water level, and more.
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables users to control a local-network iRobot Roomba over MQTT without cloud, including status monitoring, mission start/stop/pause/resume, docking, bin emptying, beeping, and plain-language error decoding.
      1
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables controlling Roborock vacuums through MCP, exposing status, rooms, cleaning history, consumables, and robot controls.
      MIT