Skip to content
Conduit

mcp server

homekit-py

talks directly to your accessories over the local network — no Apple cloud, no Apple ID, no internet required. CLI, MCP-Server and Skill

jenreh/homekit-pycommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "homekit-py": {
      "command": "uvx",
      "args": [
        "homekit-py"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 27, 2026

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 library — async 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 policy — lock.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>
CommandDescription
homekit discovermDNS browse for advertised HomeKit accessories
homekit pair DEVICE_ID --pin PINPair with an accessory (one-time)
homekit unpair DEVICE_IDRemove a stored pairing
homekit entitiesList all entities from paired accessories
homekit entity ENTITY_IDShow capability and state for one entity
homekit get ENTITY_IDFetch current state
homekit set ENTITY_ID EXPRSet state or attribute (on, brightness=70)
homekit on ENTITY_IDTurn on
homekit off ENTITY_IDTurn off
homekit brightness ENTITY_ID VALUESet brightness (0–100)
homekit color-temp ENTITY_ID KELVINSet colour temperature
homekit temperature ENTITY_ID CELSIUSSet 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 PERCENTSet the target position of a cover/window/garage
homekit identify DEVICE_IDTrigger the accessory identify action
homekit accessories DEVICE_IDDump accessory, service, and characteristic details
homekit watch [ENTITY_ID ...]Stream real-time state changes for one, many, or all entities
homekit pairings listList stored pairings
homekit pairings export --out FILEBack up pairing store to JSON
homekit pairings import FILERestore pairings from a JSON backup
homekit diagnose mdnsCheck mDNS / Bonjour health
homekit diagnose networkCheck network reachability
homekit diagnose pairabilityCheck whether discovered accessories are pairable
homekit diagnose storageVerify pairing-store integrity
homekit diagnose mcp-securityCheck MCP exposure and write-policy safety
homekit diagnose allRun every diagnostic and exit non-zero on failures
homekit raw read DEVICE_ID AID IIDRead a raw HAP characteristic
homekit raw write DEVICE_ID AID IID VALUEWrite a raw HAP characteristic
homekit daemon statusShow whether the background daemon is reachable
homekit daemon startStart or connect to the background daemon
homekit daemon stopStop the background daemon
homekit daemon restartRestart 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:

VariableOverrides
HOMEKIT_CONFIG_DIRconfig directory path
HOMEKIT_PAIRING_DIRpairing store directory
HOMEKIT_CONNECTION__REQUEST_TIMEOUT_Sconnection.request_timeout_s
HOMEKIT_MCP__ALLOW_WRITE_TOOLSmcp.allow_write_tools
HOMEKIT_DAEMON__ENABLEDdaemon.enabled
HOMEKIT_DAEMON__AUTO_SPAWNdaemon.auto_spawn
HOMEKIT_DAEMON__IDLE_TIMEOUT_Sdaemon.idle_timeout_s

Dangerous operations policy

Certain operations are gated to prevent accidental or unauthorised control:

PolicyBehaviour
allowExecutes immediately
confirmation_requiredRequires a confirmation_token argument
disabledAlways rejected

Default: lock.unlock → confirmation_required, garage.open and security_system.disarm → disabled.


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.

↑↓ navigate openesc close