{
  "markdown": "# Larnitech MCP\n\n<!-- mcp-name: io.github.mpopovych-thinkhome/larnitech-mcp -->\n\n**Version 1.3.1 Beta** · [Changelog](CHANGELOG.md) · MIT licensed\n\nAn [MCP](https://modelcontextprotocol.io) server that lets an AI agent read\nand control a [Larnitech](https://larnitech.com) smart-home installation over\nthe API2 protocol — lights, climate, blinds, sensors, meters.\n\nIt ships with a documented device-type reference built from live testing\nagainst real controllers, so the agent looks up how a widget actually\nbehaves instead of guessing from key names. That matters more than it\nsounds: on this platform writing `state: \"closed\"` to a gate is\nacknowledged with `success: true` and then silently ignored, and several\nclimate types drop half of any two-key write. Those traps are documented,\nchecked before every write, and listed in this README's own safety section.\n\n---\n\n## What it does\n\n**Reading** — open, no configuration beyond the API key.\n\n| Tool | What it does |\n|---|---|\n| `list_objects` | configured controllers (never returns keys) |\n| `check_connection` | connect, authorize, report device count |\n| `list_devices` | full snapshot, filterable by area / type / name |\n| `get_device` | status of one device by address |\n\n**Understanding what came back** — statuses are type-specific and\noccasionally not key/value at all.\n\n| Tool | What it does |\n|---|---|\n| `get_docs()` | overview of every documented device type |\n| `get_docs(\"AC\")` | full detail for one type: status keys, enums, XML attributes, script byte layout, quirks |\n| `get_docs(\"bugs\")` | numbered registry of confirmed vendor bugs |\n| `get_docs(\"protocol\")` | API2 protocol reference |\n\nResponses flag statuses that need care: an opaque `hex` blob, a\n`malfunction` fault code in place of a normal reading, or an all-`null`\npayload from a meter that missed its poll cycle — which means *no data*,\nnot zeros.\n\n**Watching** — non-blocking, for \"press the switch and tell me what moved\".\n\n| Tool | What it does |\n|---|---|\n| `watch_start` | begin watching; returns immediately |\n| `watch_read` | drain what changed since the last read, per key `from`/`to` |\n| `watch_stop` / `watch_list` | stop one / list active |\n\nA watch keeps its own connection alive, so it survives the controller's\n5-minute idle timeout and can stay open across a long conversation.\n\n**Writing** — off by default, two-phase, and never a single tool call.\n\n| Tool | What it does |\n|---|---|\n| `set_device` | validate, preview the change, return a token — **does not write** |\n| `confirm_set` | execute, then wait for the device to settle and report what actually landed |\n\n**Learning** — findings survive the session.\n\n| Tool | Writes to |\n|---|---|\n| `add_docs_note` | that device type's own doc file |\n| `add_preference` | `preferences.md`, served with every `get_docs` |\n\n**Saving data** — a snapshot is worth more than scrollback.\n\n| Tool | What it does |\n|---|---|\n| `save_snapshot` | keep a slice of controller data as a file |\n| `list_snapshots` | what has been saved, per controller |\n| `read_snapshot` | read one back, to compare against now |\n\nFiles land in `data/<controller>/<date>_<time>_<comment>.txt`, one folder\nper controller — or in a folder of your own, if that installation already\nhas a project directory holding its drawings, configs and controller\nbackups:\n\n```bash\npython -m larnitech_mcp data-dir \"Home\" \"/projects/home/backups\"\n```\n\nSnapshots then sit with the rest of that site's files instead of in a\nseparate store. `list_snapshots` finds both kinds.\n\nEach is a JSON envelope — `object`, `saved_at`, `comment`\nand the payload under `data` — so provenance travels with the file and\n`read_snapshot` hands back parsed structure rather than text to re-parse.\nNumbers stay numbers and `null` stays null, which matters here: telling\n\"no data\" apart from zero is the difference between a quiet meter and a\nreading of nothing.\n\nAsk for \"save the current state\" and the agent files it there; ask \"has\nthis changed since last week\" and it has something to compare against.\n\n**Reporting** — `report_bug` turns something you hit into a ready-to-file\nissue for this repository. It composes the report, strips identifiers\n(API keys, serials, hostnames, site names) and returns a prefilled link.\nNothing is posted: you open the link, read exactly what would be\npublished, and submit it yourself under your own account. No token is\nneeded by anyone.\n\n---\n\n## Requirements\n\n- Python 3.11+\n- A Larnitech controller you administer, reachable via the Larnitech cloud\n  or on your LAN\n\n## Install\n\n```bash\npip install larnitech-mcp\n```\n\nA virtual environment is worth using, since you'll point Claude Code at\nthat interpreter's absolute path:\n\n```bash\npython -m venv ~/.venvs/larnitech\n~/.venvs/larnitech/bin/pip install larnitech-mcp     # Windows: Scripts\\pip.exe\n```\n\nYour API keys, preferences, saved snapshots, and any device notes the agent\nrecords live in `~/.larnitech-mcp/`, outside the package, so upgrading never\ntouches them.\n\nTo work on the server itself, install from a clone instead:\n\n```bash\ngit clone https://github.com/mpopovych-thinkhome/larnitech-mcp.git\ncd larnitech-mcp\npip install -e .\n```\n\n## Where to get your API key\n\nBoth values come from **LT_Setup**, Larnitech's own configuration app, on\nthe installation you administer:\n\n| What | Where in LT_Setup |\n|---|---|\n| API key | **Security → Show API key** |\n| Serial number (cloud connection) | **General** |\n| IP address (LAN connection) | **General**, or your router's DHCP table |\n| WebSocket port (LAN, default `2041`) | **General → API → Websocket port** |\n\nThe key grants full read and write access to the installation. Treat it\nlike a password.\n\n## Connect a controller\n\nRegister it, then store the key:\n\n```bash\npython -m larnitech_mcp add \"Home\" cloud --serial YOUR_SERIAL\npython -m larnitech_mcp auth \"Home\"\n```\n\n`auth` prompts with hidden input and writes the key to\n`~/.larnitech-mcp/project_keys.json`. The key never passes through the chat\ntranscript this way — prefer it over the `object_set_key` tool, which works\nbut leaves the key in the session log.\n\nFor a controller on your LAN instead of via the cloud:\n\n```bash\npython -m larnitech_mcp add \"Home\" local --host 192.168.1.50\n```\n\nCheck it works:\n\n```bash\npython -m larnitech_mcp test \"Home\"      # connect, authorize, count devices\npython -m larnitech_mcp devices \"Home\"   # full snapshot, counts per type\n```\n\n## Add to Claude Code\n\nAdd the server to the top-level `mcpServers` object in `~/.claude.json`,\nusing the absolute path to the venv's Python:\n\n```json\n{\n  \"mcpServers\": {\n    \"larnitech\": {\n      \"command\": \"/home/you/.venvs/larnitech/bin/python\",\n      \"args\": [\"-m\", \"larnitech_mcp\", \"serve\"]\n    }\n  }\n}\n```\n\nOn Windows the command is the `.exe`, with escaped backslashes:\n\n```json\n\"command\": \"C:\\\\Users\\\\you\\\\.venvs\\\\larnitech\\\\Scripts\\\\python.exe\"\n```\n\nIt must be the top-level `mcpServers` key in `~/.claude.json` itself — a\n`.mcp.json` placed inside the `~/.claude/` folder is never read. To scope\nit to one project instead, put the same block in a `.mcp.json` at that\nproject's root. Restart Claude Code fully afterwards; closing the window\nis not enough.\n\nOther MCP clients work the same way — the server runs on stdio via\n`python -m larnitech_mcp serve`.\n\n---\n\n## Safety model\n\n**Reading is open. Writing is off until you turn it on, per controller,\nfrom a terminal:**\n\n```bash\npython -m larnitech_mcp allow-write \"Home\" on\n```\n\nThere is deliberately no tool for this — an agent cannot grant itself\nwrite access, only tell you the command.\n\n**Every write is two calls.** `set_device` validates the payload, reads\ncurrent state, and returns a preview plus a single-use token; it never\ntouches the controller. `confirm_set(token)` performs the write, waits for\nthe device to go quiet, then reports what actually landed, including\n`unrequested_changes` — anything the controller altered on its own.\n\n**Writes can be sequences,** because some types cannot be driven with one\nframe:\n\n```json\n[{\"status\": {\"mode\": \"heat\"}, \"delay_after\": 1.0},\n {\"status\": {\"state\": \"on\"}}]\n```\n\nSending `mode` and `state` together loses the `state`: the controller\nre-evaluates the channel after a mode change and overrides whatever\narrived behind it. The same applies to `vent` (`state` + `fan`) and to\nclearing an `automation` before switching a channel off. `set_device`\nrejects the combined forms and tells you the sequence to use instead.\n\n**No key is ever returned by a tool,** and keys are masked out of error\nmessages.\n\n## Device documentation\n\n`larnitech_mcp/docs/device-types/` holds one file per device type plus an\nindex, covering the API2 status keys, XML attributes, script-side byte\nlayout, and every quirk confirmed by live testing. `bugs.md` alongside it is\na numbered registry of confirmed vendor bugs that the type files reference.\nRead them through `get_docs` rather than by path — that also picks up\nanything you've added locally.\n\nNotes the agent records with `add_docs_note` go to `~/.larnitech-mcp/docs/`,\nnot into the installed package, so they survive upgrades. Your copy wins on\nread; everything you haven't edited still comes from the shipped set.\n\nThis is a working knowledge base, not a spec: entries say plainly when\nsomething is confirmed live, observed but unexplained, or still unknown.\nCorrections and additions are welcome — that is the most valuable kind of\ncontribution here.\n\n## Command reference\n\n| Command | Effect |\n|---|---|\n| `add <name> cloud --serial S` | register a cloud controller |\n| `add <name> local --host H [--port P]` | register a LAN controller |\n| `auth <name>` | store its API key (hidden prompt) |\n| `list` | configured controllers, key presence |\n| `test <name>` | connect, authorize, count devices |\n| `devices <name> [--full]` | full snapshot, decoded |\n| `allow-write <name> on\\|off` | enable or disable writes |\n| `remove <name>` | drop a controller and its key |\n| `serve` | run the MCP server on stdio |\n\n## Status\n\nBeta. Reading, watching, and writing all work and have been exercised\nagainst live hardware, but this has been tested against a limited set of\ninstallations. Device types documented as unconfirmed genuinely are —\nsee `get_docs` output and the per-type files.\n\nNot affiliated with or endorsed by Larnitech.\n\n## License\n\n[MIT](LICENSE)\n\n## Contact\n\nMykhailo Popovych\n- Telegram: [t.me/M_Popovych_ThinkHome](https://t.me/M_Popovych_ThinkHome)\n- Phone (WhatsApp): +370 632 89 991, +380 99 333 99 96\n- Email: [m.popovych@thinkhome.io](mailto:m.popovych@thinkhome.io)\n",
  "bytes": 10538,
  "sha": "5f40b28eb6aa37737b5fbd83d5f9e3441a4ed1e8d6b5c453b7ebf0f5bec3233e",
  "repo_slug": "mpopovych-thinkhome/larnitech-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_mpopovych_thinkhome_larnitech__5de1fb1d/readme"
}