Skip to main content

Local control of Apple HomeKit devices via HAP: library, CLI, and MCP server

Project description

homekit-py

Version Python License: MIT

homekit-py talks directly to your accessories over the local network — no Apple cloud, no Apple ID, no internet required. HAP is cryptographically complex (SRP, Ed25519, Curve25519, ChaCha20-Poly1305, TLV8); this project delegates the wire protocol to aiohomekit and wraps it in a stable HomeKitBackend interface with a clean entity model.


Features

  • Entity model — lights, switches, sensors, locks, thermostats, covers, fans mapped to stable domain.slug IDs
  • Async Python libraryasync with HomeKitClient(config) as client: ...
  • Rich CLI — human-readable tables or --json for scripts
  • MCP server — expose your accessories as tools to Claude or any MCP client
  • On-disk state cache — fast repeated reads, configurable TTL
  • Dangerous-operations policylock.unlock, garage.open, security_system.disarm gated by policy and confirmation tokens
  • mDNS discovery — find all accessories on the LAN in seconds
  • HAP event subscriptions — real-time characteristic change events via homekit watch

Installation

pip install homekit-py
# or with uv
uv add homekit-py

Requires Python 3.14+. Pairing material is stored via the OS keychain (keyring) with an encrypted file fallback in ~/.config/homekit-local/pairings/.


Quick start

1. Discover accessories

homekit discover
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Name               ┃ Device ID         ┃ Model           ┃ Category ┃ Host:Port       ┃ State  ┃
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
│ Living Room Light  │ AA:BB:CC:DD:EE:FF │ Eve Light Strip │ Lighting │ 192.168.1.42:80 │ pairable│
└────────────────────┴───────────────────┴─────────────────┴──────────┴─────────────────┴────────┘

2. Pair the accessory

[!IMPORTANT] An accessory can only be paired with one controller at a time. If the device is already paired (with Apple Home, another homekit-py install, Home Assistant, etc.) the homekit discover output will show paired and homekit pair will fail with AlreadyPairedError.

Before pairing, remove the device from its current controller:

  • Apple Home: open the Home app → tap the accessory → Remove Accessory (do not factory-reset unless instructed by the vendor).
  • Other controller: run its unpair equivalent.
  • Lost the keys: factory-reset the device per the vendor's instructions (typically a long button press).

After removal the device re-advertises with sf=1 (pairable) within a few seconds; re-run homekit discover to confirm.

Enter the 8-digit PIN from the accessory's label or display:

homekit pair AA:BB:CC:DD:EE:FF --pin 123-45-678 --alias "Living Room"

Pairing data is saved to ~/.config/homekit-local/pairings/. You only do this once.

3. Control

# List all entities
homekit entities

# Get current state
homekit get light.living_room

# Turn on / off
homekit on light.living_room
homekit off light.living_room

# Set brightness and colour temperature
homekit brightness light.living_room 60
homekit color-temp light.living_room 2700

# Set thermostat
homekit temperature climate.hallway 21.5

# Watch real-time events
homekit watch light.living_room

CLI reference

homekit [--verbose] [--no-daemon] <command>
Command Description
homekit discover mDNS browse for advertised HomeKit accessories
homekit pair DEVICE_ID --pin PIN Pair with an accessory (one-time)
homekit unpair DEVICE_ID Remove a stored pairing
homekit entities List all entities from paired accessories
homekit entity ENTITY_ID Show capability and state for one entity
homekit get ENTITY_ID Fetch current state
homekit set ENTITY_ID EXPR Set state or attribute (on, brightness=70)
homekit on ENTITY_ID Turn on
homekit off ENTITY_ID Turn off
homekit brightness ENTITY_ID VALUE Set brightness (0–100)
homekit color-temp ENTITY_ID KELVIN Set colour temperature
homekit temperature ENTITY_ID CELSIUS Set thermostat target
homekit lock ENTITY_ID [--confirm TOKEN] Lock a lock entity
homekit unlock ENTITY_ID [--confirm TOKEN] Unlock a lock entity
homekit position ENTITY_ID PERCENT Set the target position of a cover/window/garage
homekit identify DEVICE_ID Trigger the accessory identify action
homekit accessories DEVICE_ID Dump accessory, service, and characteristic details
homekit watch [ENTITY_ID ...] Stream real-time state changes for one, many, or all entities
homekit pairings list List stored pairings
homekit pairings export --out FILE Back up pairing store to JSON
homekit pairings import FILE Restore pairings from a JSON backup
homekit diagnose mdns Check mDNS / Bonjour health
homekit diagnose network Check network reachability
homekit diagnose pairability Check whether discovered accessories are pairable
homekit diagnose storage Verify pairing-store integrity
homekit diagnose mcp-security Check MCP exposure and write-policy safety
homekit diagnose all Run every diagnostic and exit non-zero on failures
homekit raw read DEVICE_ID AID IID Read a raw HAP characteristic
homekit raw write DEVICE_ID AID IID VALUE Write a raw HAP characteristic
homekit daemon status Show whether the background daemon is reachable
homekit daemon start Start or connect to the background daemon
homekit daemon stop Stop the background daemon
homekit daemon restart Restart the background daemon
homekit daemon logs [-n LINES] Tail the daemon log file

Many read-style commands accept --json for machine-readable output. Place it after the command name:

homekit entities --json | jq '.[].entity_id'

Python library

import asyncio
from homekit import HomeKitClient, load_config


async def main():
    async with HomeKitClient(load_config()) as client:
        # List all entities
        for entity in await client.list_entities():
            print(entity.entity_id, entity.domain, entity.name)

        # Read state
        state = await client.get_state("light.living_room", refresh=True)
        print(state.state, state.attributes)

        # Control
        await client.turn_on("light.living_room")
        await client.set_brightness("light.living_room", 60.0)
        await client.set_color_temperature("light.living_room", 2700)
        await client.set_target_temperature("climate.hallway", 21.5)


asyncio.run(main())

MCP server

homekit-py ships with an MCP server that exposes your accessories as tools for Claude or any MCP-compatible client.

homekit-mcp                                          # STDIO (default)
homekit-mcp --transport streamable-http --host 127.0.0.1 --port 8765

[!WARNING] The MCP server is read-only by default. Set [mcp].allow_write_tools = true in ~/.config/homekit-local/config.toml to expose write tools.

Claude Desktop

{
  "mcpServers": {
    "homekit": {
      "command": "homekit-mcp"
    }
  }
}

VS Code (agent mode)

{
  "mcp": {
    "servers": {
      "homekit": {
        "command": "homekit-mcp",
        "type": "stdio"
      }
    }
  }
}

Available MCP tools

Read (always available)

homekit_list_entities · homekit_get_state

Write (requires allow_write_tools = true)

homekit_set_light · homekit_set_switch · homekit_set_climate · homekit_set_cover · homekit_lock · homekit_unlock

Resources

homekit://entities · homekit://entities/{entity_id} · homekit://state/{entity_id} · homekit://capabilities/{entity_id} · homekit://events/recent


Configuration

Config file: ~/.config/homekit-local/config.toml

[controller]
name = "homekit-local"

[discovery]
mdns_timeout_s = 15.0
ip_only = false

[connection]
mode = "ondemand"          # "ondemand" | "persistent"
request_timeout_s = 10.0

[cache]
ttl_seconds = 3600

[storage]
backend = "keyring"        # "keyring" | "file"

[mcp]
allow_write_tools = false
allow_raw_characteristic_writes = false
audit_log = true

[daemon]
enabled = true
auto_spawn = true
idle_timeout_s = 600      # seconds; 0 = never shut down

[dangerous_operations]
"lock.unlock" = "confirmation_required"
"garage.open" = "disabled"
"security_system.disarm" = "disabled"
"cover.open" = "allow"

See docs/config.toml.example for a fully-annotated reference with every available key.

Environment variable overrides:

Variable Overrides
HOMEKIT_CONFIG_DIR config directory path
HOMEKIT_PAIRING_DIR pairing store directory
HOMEKIT_CONNECTION__REQUEST_TIMEOUT_S connection.request_timeout_s
HOMEKIT_MCP__ALLOW_WRITE_TOOLS mcp.allow_write_tools
HOMEKIT_DAEMON__ENABLED daemon.enabled
HOMEKIT_DAEMON__AUTO_SPAWN daemon.auto_spawn
HOMEKIT_DAEMON__IDLE_TIMEOUT_S daemon.idle_timeout_s

Dangerous operations policy

Certain operations are gated to prevent accidental or unauthorised control:

Policy Behaviour
allow Executes immediately
confirmation_required Requires a confirmation_token argument
disabled Always rejected

Default: lock.unlockconfirmation_required, garage.open and security_system.disarmdisabled.


Docs


Development

git clone https://github.com/jenreh/homekit-py
cd homekit-py
uv sync
task test     # pytest with coverage
task lint     # ruff + mypy
task format   # ruff format

[!NOTE] A FakeBackend simulator (tests/fake_backend.py) is included for use in tests. It stubs the HomeKitBackend interface without requiring real accessories or network access.

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

homekit_py-0.4.5.tar.gz (56.9 kB view details)

Uploaded Source

Built Distribution

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

homekit_py-0.4.5-py3-none-any.whl (59.7 kB view details)

Uploaded Python 3

File details

Details for the file homekit_py-0.4.5.tar.gz.

File metadata

  • Download URL: homekit_py-0.4.5.tar.gz
  • Upload date:
  • Size: 56.9 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 homekit_py-0.4.5.tar.gz
Algorithm Hash digest
SHA256 46ede8fcdd7c73f7f61504de059a710e128e6938db2284576afcb4f4f0a1ab77
MD5 3d31f5599fae62586bbdb6095669fc3d
BLAKE2b-256 75cd1d4e2c3c3e33c6445d2264ed8cd29c56480806027e7a50c44d79e56d8dfd

See more details on using hashes here.

File details

Details for the file homekit_py-0.4.5-py3-none-any.whl.

File metadata

  • Download URL: homekit_py-0.4.5-py3-none-any.whl
  • Upload date:
  • Size: 59.7 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 homekit_py-0.4.5-py3-none-any.whl
Algorithm Hash digest
SHA256 fac2b9fae07618d0410f6235b832d85bfe21812034a809990aed33a1ef9e663b
MD5 8b962c274cd16ce64b387eeec080aeeb
BLAKE2b-256 480f8df88922df7118a6ff330ae4abdc74fedacc6165b131a2fe7ee47eacd172

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