Controls your Fritz Home thermostates from the command line or via the MCP protocol.
Project description
fritzctl — FRITZ!Home CLI & MCP Server
fritzctl controls FRITZ!DECT radiator thermostats directly via your FRITZ!Box — no cloud, no account, no internet required after first setup.
Features
- Pure local control — authenticates against your FRITZ!Box via challenge-response; all traffic stays on your LAN
- Dual API support — Smart Home REST API (FRITZ!OS 8.20+) with automatic fallback to the legacy AHA XML API
- Rich CLI — human-readable tables or
--jsonfor scripts and pipes - MCP server — expose your thermostats as tools to Claude or any MCP client
- Safety layer — enforces temperature bounds (8–28 °C), max delta (5 °C/operation), cooldown windows, and device lock/battery checks before every write
- Audit log — every write operation is recorded to
~/.config/fritz-local/audit.log - Hub discovery — SSDP/UPnP M-SEARCH scan; no IP guessing needed
- Keyring integration — credentials stored securely in the OS keyring, never in plain text
Installation
pip install fritzctl
Requires Python 3.14+.
Quick start
1. Discover your FRITZ!Box
fritzctl discover
FRITZ!Box Devices Found
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ IP ┃ URL ┃ Model ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ 192.168.178.1 │ http://192.168.178.1 │ FRITZ!Box 7590 │
└───────────────┴───────────────────────┴─────────────────┘
2. Authenticate and save credentials
fritzctl setup
Credentials are saved to the OS keyring and ~/.fritzhome/config.json. You only need to do this once.
3. Use it
fritzctl list
fritzctl status "Wohnzimmer"
fritzctl set-temp "Wohnzimmer" 21.5
fritzctl enable-boost "Kinderzimmer" --duration-seconds 600
CLI reference
fritzctl [--json] <command> [args]
| Command | Description |
|---|---|
fritzctl discover |
Scan the LAN for FRITZ!Box devices via SSDP/UPnP |
fritzctl setup |
Authenticate and persist credentials to keyring |
fritzctl health |
Check FRITZ!Box connectivity and API type |
fritzctl list |
List all thermostats and groups |
fritzctl status [DEVICE] |
Show temperature, battery, and lock state |
fritzctl set-temp DEVICE TEMP |
Set target temperature (°C) with safety check |
fritzctl enable-boost DEVICE |
Activate boost mode for a duration |
fritzctl disable-window-open DEVICE |
Cancel window-open mode early |
All write commands support --dry-run (plan only, no execution) and --no-confirm (skip interactive prompt).
Exit codes
| Code | Meaning |
|---|---|
0 |
success |
1 |
error (authentication, network, safety violation) |
Safety guardrails
Every write command is checked by the SafetyPolicyEngine before execution:
- Temperature must be between 8 °C and 28 °C
- Change cannot exceed ±5 °C in a single operation
- Device must be online, unlocked, and have battery > 10 %
- A cooldown window prevents repeat writes in quick succession
[!NOTE] Device names or AIns are both accepted as identifiers. If a device is temporarily offline, the local cache (
~/.fritzhome/config.json) is used to resolve names to AIns.
MCP server
fritzctl ships with an MCP server that exposes your thermostats as tools for Claude or any MCP-compatible client.
[!WARNING] Write tools are disabled by default. Set
FRITZ_MCP_ALLOW_WRITES=trueto enableset_heater_temperature,enable_boost, anddisable_window_open.
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"fritz": {
"command": "fritzctl-mcp",
"args": [],
"env": {
"FRITZ_BOX_URL": "http://192.168.178.1",
"FRITZ_MCP_ALLOW_WRITES": "true"
}
}
}
}
VS Code (agent mode)
{
"mcp": {
"servers": {
"fritz": {
"command": "fritzctl-mcp",
"type": "stdio",
"env": {
"FRITZ_BOX_URL": "http://192.168.178.1",
"FRITZ_MCP_ALLOW_WRITES": "true"
}
}
}
}
}
Available MCP tools
list_heaters · get_heater_status · get_group_status · set_heater_temperature · enable_boost · disable_window_open
Configuration
Credentials and URL are stored in ~/.fritzhome/config.json (non-sensitive) and the OS keyring (password). You can also use environment variables:
| Variable | Description |
|---|---|
FRITZ_BOX_URL |
FRITZ!Box base URL (default: http://192.168.178.1) |
FRITZ_BOX_USERNAME |
Username (leave unset for password-only boxes) |
FRITZ_BOX_PASSWORD |
Password fallback when keyring is unavailable |
FRITZ_MCP_ALLOW_WRITES |
Set to true to enable write tools in the MCP server |
Precedence: keyring / config file > env var > default.
Python library
import asyncio
from fritzctl.avm.clients import fritz_client_context
async def main() -> None:
async with fritz_client_context() as client:
devices = await client.list_devices()
for d in devices:
print(f"{d.name}: {d.current_temp:.1f}°C → {d.target_temp:.1f}°C")
await client.set_temperature("12345 67890", 21.0)
asyncio.run(main())
Development
git clone https://github.com/jenreh/fritzhome-py
cd fritzhome-py
uv sync
task test # pytest with coverage
task lint # ruff + mypy
task format # ruff format
[!NOTE] Tests use
pytest-httpserverto mock the FRITZ!Box HTTP API — no real hardware required.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fritzctl_py-0.2.0.tar.gz.
File metadata
- Download URL: fritzctl_py-0.2.0.tar.gz
- Upload date:
- Size: 19.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58d74eceac7dae7d0f73de36e8738c52834137f5bfcce8ce761f3767d30cdf4a
|
|
| MD5 |
0397ae34e011fc15a37f98619a5554d1
|
|
| BLAKE2b-256 |
9e1bf4f94498011f12d770358145588e7969e640877e6f94d00cf87533eeec59
|
File details
Details for the file fritzctl_py-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fritzctl_py-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a0eaff0a6ce32ff20bcab1a1be224fc16e2a5548d2ec14c7e2c9d4fc3cb1fd3
|
|
| MD5 |
c273322fe7a04e39feb90c2dc5e0851c
|
|
| BLAKE2b-256 |
fcac52f34f2539dd043108497376a2cd0f024b20e38968cf0be178abc3ff79ef
|