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.
Metadata
Release files for fritzctl-py 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fritzctl_py-0.2.0.tar.gz | 19.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fritzctl_py-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 42.2 kB
Release files / fritzctl_py-0.2.0.tar.gz
| Download URL | fritzctl_py-0.2.0.tar.gz |
|---|---|
| Size | 19.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
58d74eceac7dae7d0f73de36e8738c52834137f5bfcce8ce761f3767d30cdf4a
|
|
BLAKE2b-256 checksum How to use checksums |
9e1bf4f94498011f12d770358145588e7969e640877e6f94d00cf87533eeec59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / fritzctl_py-0.2.0-py3-none-any.whl
| Download URL | fritzctl_py-0.2.0-py3-none-any.whl |
|---|---|
| Size | 22.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0a0eaff0a6ce32ff20bcab1a1be224fc16e2a5548d2ec14c7e2c9d4fc3cb1fd3
|
|
BLAKE2b-256 checksum How to use checksums |
fcac52f34f2539dd043108497376a2cd0f024b20e38968cf0be178abc3ff79ef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|