Skip to main content

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.

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)

Source distribution for fritzctl-py 0.2.0
File Size Uploaded
fritzctl_py-0.2.0.tar.gz 19.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fritzctl-py 0.2.0
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page