Skip to main content

Controls your Fritz Home thermostates from the command line or via the MCP protocol.

Project description

fritzctl — FRITZ!Home CLI & MCP Server

Version License: MIT Python

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 --json for 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=true to enable set_heater_temperature, enable_boost, and disable_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-httpserver to mock the FRITZ!Box HTTP API — no real hardware required.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fritzctl_py-0.2.0.tar.gz (19.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fritzctl_py-0.2.0-py3-none-any.whl (22.9 kB view details)

Uploaded Python 3

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

Hashes for fritzctl_py-0.2.0.tar.gz
Algorithm Hash digest
SHA256 58d74eceac7dae7d0f73de36e8738c52834137f5bfcce8ce761f3767d30cdf4a
MD5 0397ae34e011fc15a37f98619a5554d1
BLAKE2b-256 9e1bf4f94498011f12d770358145588e7969e640877e6f94d00cf87533eeec59

See more details on using hashes here.

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

Hashes for fritzctl_py-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0a0eaff0a6ce32ff20bcab1a1be224fc16e2a5548d2ec14c7e2c9d4fc3cb1fd3
MD5 c273322fe7a04e39feb90c2dc5e0851c
BLAKE2b-256 fcac52f34f2539dd043108497376a2cd0f024b20e38968cf0be178abc3ff79ef

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page