OmniButler
Connects to a Home Assistant instance using its API (HA_URL, HA_TOKEN) to control and read state of devices, translating Home Assistant domains into the unified capability model for use with MCP tools.
Provides a local miIO driver that translates Xiaomi MiOT-Spec services into a unified capability model (onoff, target_temperature, etc.), enabling control of Xiaomi smart home devices through the MCP server.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@OmniButlerset the living room AC to 24 degrees and turn on the air purifier"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Tiybai OmniButler
An open smart-device bridge that lets AI agents - Muse, OpenClaw, Hermes, any MCP client - control the smart devices in your life, and run personal scenes for you, deterministically and locally.
中文简介:Tiybai OmniButler(万能管家)是一个开源智能设备桥。它把小米、涂鸦、美的等 各家互不相通的智能设备,统一成一个能力模型,再通过 MCP 开放给 AI Agent 使用; AI 负责听懂你的意图、编排场景,真正执行的是本地的确定性规则引擎。高风险设备 (门锁、车库门、燃气)默认不交给 AI 直接控制,必须经人工确认。完整中文说明见 README.zh-CN.md。
Not an official product of Xiaomi, Tuya, Midea, Home Assistant, Anthropic or any other vendor mentioned. All trademarks belong to their owners.
Why
Smart devices in a typical home speak a dozen incompatible protocols and live in a dozen vendor clouds. Meanwhile AI agents speak MCP. OmniButler sits in the middle:
One capability model - drivers translate MiOT-Spec services, Tuya data points, Matter clusters and Home Assistant domains into one vocabulary:
onoff,target_temperature,pm25,position, ...One agent interface - a dependency-free MCP server (stdio, plus an optional token-authenticated HTTP transport) with twelve tools. Any MCP client can use it. Note there is deliberately no "approve" tool: confirmations are human-only, from a terminal on the host.
Scenes that run without the AI - YAML rules (trigger + conditions + actions) executed by a local deterministic engine. The AI authors and tunes scenes; it is never in the real-time control loop.
Safety guardrails in code - high-risk actions are parked in a confirmation queue for a human. Every control call lands in a local, append-only audit log. An optional webhook (
OMNIBUTLER_NOTIFY_WEBHOOK_URL) pings your phone the moment something queues up, so a headless bridge never waits in silence.
Related MCP server: iotforge
Architecture
agents (MCP) -> mcp_server -> scene engine -> core (model/registry/
events/audit/manager)
|
drivers: mock | homeassistant | miio |
tuya | midea | broadlink |
matter | zigbee2mqtt | terminals
|
device-data (per-model facts)See docs/architecture.md for the five layers and the five device access modes.
Quick start (5 minutes, no hardware needed)
Requires Python 3.11+.
git clone https://github.com/Tiybai/tiybai-omnibutler.git
cd tiybai-omnibutler
pip install -e .
tob devices # a virtual home: 2 ACs, purifier, light, curtain, scale, garage door, robot vacuum
tob devices --state # ... with live state
tob set living_ac onoff true
tob set living_ac target_temperature 24
tob scenes # list + validate the bundled scene pack
tob simulate # fire "arrive home": watch the scene chain execute
tob simulate --event leave # fire "leave home": everything turns off
tob simulate --event pm25 # fire a PM2.5 spike: the purifier goes turbo
tob simulate --event garage # fire "arrive at garage gate": the door action is QUEUED for confirmationConnect a real home instead of the mock one:
export HA_URL=http://192.168.1.10:8123
export HA_TOKEN=<your Home Assistant long-lived token>
tob --driver homeassistant devicesLet an AI agent drive it (MCP)
tob mcp # serves MCP on stdioPoint any MCP client at that command. Example client configuration:
{
"mcpServers": {
"omnibutler": { "command": "tob", "args": ["mcp"] }
}
}Tools: list_devices, get_device_state, set_device_property,
call_device_action, list_scenes, enable_scene,
get_pending_confirmations. That is the whole list - there is no
tool to approve a queued action. Approving is a human-only step on the
host: tob pending, then tob confirm <id> (or tob reject <id>).
For agents that do not run on the host, tob mcp --http serves the same
tools over HTTP on 127.0.0.1:8765, protected by a bearer token from
$OMNIBUTLER_HTTP_TOKEN. For remote access put it behind Cloudflare
Access or WireGuard; do not expose it raw.
Approving without a terminal: tob approvals serves a small local
web page (its own password, $OMNIBUTLER_APPROVALS_TOKEN, localhost by
default) listing what is waiting, with Approve / Reject buttons - or
run tob run --notify on a Mac and a native dialog pops up when
something queues. Both are human clicks outside the MCP surface; the AI
still cannot approve anything. On the Mac dialog, Reject is the default
button and silence never means yes.
Run it as an always-on butler with tob run (schedule triggers and
device-state polling), check a real setup with tob doctor, and get
plain-language help fetching a device key with tob setup miio /
tob setup tuya. A fetched key can be stored without ever appearing
on screen: tob setup-secret miio.devices.0.token prompts with
hidden input and writes the config file with 0600 permissions. New in v0.4: tob onboard scans every driver and
says in plain language what each found device still needs,
tob fetch-keys xiaomi|tuya fetches local keys from the vendor cloud
once (with your own account, only when no captcha/2FA blocks it), and
tob gateway accepts phone data streams and geofence events;
tob streams shows what the phone has sent, tob discover lists what
each driver can see on the network, and tob state prints one
device's full current state. Day to day, tob audit reads the audit
log back, and tob backup / tob restore pack config and state into
one file for moving the butler to another machine.
High-risk devices (the garage door in the demo home) refuse direct tool
calls by design. Try it: ask the agent to open the garage door, then run a
scene that requests it (examples/scenes/garage-arrival.yaml) and watch the
action land in get_pending_confirmations instead of executing.
Scenes
Scenes are small YAML files - see examples/scenes/:
Scene | Trigger | What happens |
| geofence enter | Living-room AC on at 26 C, purifier on auto |
| geofence exit | Everything off, so nothing is left running |
| 22:30 | Bedroom sleep temperature, curtain closes, purifier silent |
| PM2.5 state change | Purifier to turbo while PM2.5 > 75 |
| geofence (garage gate) | Door action queued for human confirmation (high risk); light runs |
Project status & roadmap
v0.10.3 (this release) - the audit round: four independent audits
(cross-platform, security and concurrency, data layer and
performance, docs and UI alignment) went over the whole codebase,
and every finding was fixed or consciously accepted. The
confirmation queue is now locked across processes, and a queued
action is claimed atomically before it runs - approving the same
item from the web page and the CLI can no longer execute it twice.
Saving secrets no longer crashes on Windows. The phone gateway
only accepts geofence and presence events; it can no longer forge
state changes to trigger scenes. Reading the audit log is now a
streaming pass (tob audit --last 20 on a full 60 MB log: 1.8 s
and 362 MB of memory before, 0.13 s and ~45 MB now) and data
streams load lazily, so everyday commands stay fast no matter how
much history has piled up. Polling runs drivers in parallel, and
one dead device no longer stalls the rest of its driver. The
approvals page follows your browser language (Chinese or English)
and works properly on a phone screen. CI now also runs on Windows
and macOS runners, and tob setup finally lists every guide it
actually has - five topics were unreachable from the command line.
The .1 patch fixes what the first Windows and macOS CI runs caught
(a Python 3.13 threading collision in the miio test fakes, a few
POSIX-only test assumptions, and a gitleaks allowlist that only
worked in local scans) plus an upstream breakage: python-broadlink
1.0 now requires Python 3.13 and its old releases are gone from
PyPI, so the Broadlink extra skips itself on older Pythons instead
of failing the whole install. The .2 patch skips the POSIX-probe
test on Windows hosts: forcing the kill(0) path there can broadcast
a console Ctrl+C and interrupt the test runner itself - the very
hazard the Win32 probe branch exists to avoid. The .3 patch makes
the license-gate tests platform-true: they asserted candidate
selection by executing a POSIX shell script, which Windows cannot
run - selection is now a pure function, asserted directly on every
OS, with the shell-script end-to-end kept on POSIX runners.
Real-hardware verification moves to v0.11 - it still needs owners
of real devices (issues #2 and #4).
v0.9 - the final sweep: scenes gain delayed actions
(- delay: 300 - a light that turns itself off, a vacuum that starts
once you have left); the Home Assistant driver subscribes to HA's
event stream, so state changes reach scenes near-instantly instead of
on the 30-second poll; third-party drivers can ship as pip packages
(entry point group omnibutler.drivers), no fork needed; tob audit
and tob backup / tob restore cover the operator chores; the
daemon refuses to start twice against the same state directory; CI
adds a coverage floor (80%, currently 88%) and Python 3.13 to the
test matrix; device-data reaches 20 profiles and the demo home gains
a robot vacuum. Real-hardware verification moves to v0.11 - it still
needs owners of real devices (issues #2 and #4).
v0.8 - robot vacuums join the Xiaomi local driver
(start / stop / return to dock, battery level) through the standard
MIOT vacuum service, and the Xiaomi cloud fallback inherits the new
family automatically; a queued high-risk action can now push a
webhook notification (OMNIBUTLER_NOTIFY_WEBHOOK_URL or
notify.webhook_url in config - ntfy, Bark and similar services all
take the POST), so a bridge running headless no longer queues in
silence; scene schedules accept days, so rules like "weekdays at
07:30" finally work; and CI gains lint (ruff) and type-check (mypy)
gates, both green.
v0.7 - depth where it counts: the Xiaomi driver now
covers lights, fans and humidifiers (standard MIOT services) next to
air conditioners and purifiers - and the Xiaomi cloud fallback picks
the new families up automatically; the Tuya driver controls curtain
motors (open/close + position), matching its device-data profile;
scene state conditions accept for_seconds, so a rule can require
"PM2.5 above 75 for 10 minutes" instead of reacting to a blip; and
the audit log and data streams rotate by size (default 10 MiB x 5,
tunable via OMNIBUTLER_LOG_MAX_MB / OMNIBUTLER_LOG_KEEP), so a
long-running butler cannot fill the disk. tob doctor reports the
state directory's total size.
v0.6 - the second completeness pass: a Xiaomi cloud
fallback driver (--driver xiaomi_cloud) joins the Tuya one - when
a Xiaomi device cannot be reached locally (no token, not on this
network), it can be controlled through the vendor cloud using the
same login machinery as tob fetch-keys and the same MIOT property
mapping as the local driver. Like tuya_cloud it is deliberately
not part of all. device-data grows to 14 profiles (Zigbee door
and motion sensors, a generic Matter plug, a Tuya curtain motor).
v0.5 - the completeness pass: nothing half-wired is
left. The MCP surface now covers data streams and terminal sessions
(12 tools); the phone gateway can run inside the daemon
(tob run --gateway, one process, scenes fire exactly once);
tob doctor also checks the Matter controller and Zigbee2MQTT
broker for reachability and reports config-version health; config
files are versioned (missing version = v1, newer-than-supported is
a hard error, first rewrite keeps a one-time .bak); a Tuya cloud
fallback driver (--driver tuya_cloud) controls devices through
the vendor cloud when no local path exists - it is deliberately
not part of all, going cloud is always an explicit choice; and
tob setup now has plain-language guides for matter, zigbee2mqtt,
gateway and tuya_cloud. Everything is still tested against fake
devices only - real-hardware verification remains the project's
biggest gap (issues #2 and #4).
v0.2 - first real local drivers behind clean-room specs; device-data contributions open
v0.3 - human-only confirmations enforced; daemon; MCP over HTTP; doctor + setup guides; Midea and Broadlink drivers
v0.4 - Matter (controller client) + Zigbee2MQTT drivers; phone gateway + data streams; terminal sessions; onboard scan + cloud key fetch; Docker/service packaging
v0.5 - MCP streams/sessions tools; gateway inside the daemon; doctor for the new drivers; config versioning live; Tuya cloud fallback driver; setup guides for all drivers
v0.6 - Xiaomi cloud fallback driver (cloud fallbacks now cover both brands with a usable public cloud API); device-data at 14 profiles
v0.7 - retention caps for logs and streams; Xiaomi lights / fans / humidifiers; Tuya curtain motors; sustained scene conditions (
for_seconds)v0.8 - Xiaomi robot vacuums; webhook notification when a high-risk action queues; scene schedules accept
days; ruff + mypy gates in CIv0.9 - delayed scene actions; HA event-stream subscription; third-party drivers via entry points; audit / backup commands; daemon single-instance lock; coverage gate in CI
v0.10 - the audit round: Windows/macOS in CI; confirmation queue locked and claimed atomically; gateway event whitelist; streaming audit reads and lazy stream loading; parallel polling; bilingual approvals page
v0.11 - real-hardware verification round (needs owners of real devices - see issues #2 and #4)
Later - PyPI release (needs the maintainer's PyPI account)
Contributing a device
Device support grows one verified model at a time:
Add a data file in
device-data/(facts only, withsourceandprovenance- seedevice-data/README.md).If a driver change is needed, follow CONTRIBUTING.md: real-device verification is required, and anything informed by a reference implementation goes through the clean-room process.
Original implementations only. We learn protocols and facts from the ecosystem; we do not port other projects' code.
Security
Risk levels, key handling, allowlists, audit and remote-access rules are in docs/security.md. Short version: keys stay on your machine, nothing is exposed to the public internet (remote access only via Cloudflare Access or WireGuard), and high-risk devices need a human.
Licence
Apache-2.0. See LICENSE and NOTICE. The licence audit for everything this project depends on or references is in docs/license-audit.md.
This server cannot be deployed
Maintenance
Related MCP Connectors
Context and control for any AI. Agent identity, scoped access, and proof across tools.
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Connect your AI to your own devices, 3D printers, factory tools and a shared agent task pool.
Security gateway for AI agents: policy, approval, and audited execution, no secrets shared.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables registration, monitoring, and control of IoT devices via AI agents, with local storage and no cloud API key required.9MIT
- AlicenseBqualityDmaintenanceEnables AI assistants to discover and control IoT devices on local networks and via Bluetooth LE.71MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to read Home Assistant entity states, call any service, and optionally apply named scene presets from a catalog using plain-English descriptions.48 npm1MIT
- AlicenseNot gradedqualityBmaintenanceEnables smart home automation via MCP with human-in-the-loop approval gates, providing device discovery, state queries, policy checks, and proposal-based execution of actions and scenes that only proceed after explicit user confirmation, with full audit logging.MIT