Skip to main content
Glama
pierresouchay

mcp-tahoma

README.md
# mcp-tahoma

An MCP server for a Somfy TaHoma box, built from the local API's OpenAPI
descriptor (`src/mcp_tahoma/tahoma-openapi.yml`) with [FastMCP](https://gofastmcp.com).

Everything happens on your local network: the server talks to the box directly,
no Somfy cloud involved.

## 1. Enable developer mode and get a token

In the **TaHoma by Somfy** app: *Configure the installation* → *Access the
parameters* of your box → tap **7 times** on the gateway PIN (e.g.
`2001-1234-5678`) to reveal **Developer Mode**, then generate a token.

A token is only readable at creation time, so save it right away.

## 2. Configure

Copy `.env.example` to `.env` and fill in the token:

```sh
cp .env.example .env
```

| Variable                  | Default          | Meaning                                                     |
| ------------------------- | ---------------- | ----------------------------------------------------------- |
| `TAHOMA_TOKEN`            | *(required)*     | Developer mode token                                         |
| `TAHOMA_PIN`              | *(discovered)*   | Gateway PIN, e.g. `2001-1234-5678`; needed if you own several boxes |
| `TAHOMA_HOST`             | *(discovered)*   | Hostname or IP of the box, to skip mDNS discovery entirely   |
| `TAHOMA_PORT`             | `8443`           | Local API port                                               |
| `TAHOMA_VERIFY_SSL`       | `true`           | Verify the gateway certificate (see [TLS](#tls))             |
| `TAHOMA_CA_BUNDLE`        | bundled Overkiz CA | Certificate authority to trust                             |
| `TAHOMA_TIMEOUT`          | `30`             | HTTP timeout, in seconds                                     |
| `TAHOMA_DISCOVERY_TIMEOUT`| `5`              | mDNS discovery timeout, in seconds                           |
| `TAHOMA_OPENAPI_SPEC`     | `tahoma-openapi.yml` | Path to the OpenAPI descriptor                           |

## 3. Check that it works

```sh
uv run mcp-tahoma --discover   # list the boxes visible on this network
uv run mcp-tahoma --check      # connect, verify the token, list your devices
```

`--check` prints something like:

```
Gateway  : 2001-1234-5678 (mDNS hostname)
Base URL : https://gateway-2001-1234-5678.local:8443/enduser-mobile-web/1/enduserAPI
TLS      : certificate verified, hostname verified
API      : 2022.1.3-1
Token    : accepted, 12 device(s)
  - Living room shutter	io://2001-1234-5678/12345678
  ...
```

## 4. Plug it into an MCP client

Claude Code:

```sh
claude mcp add tahoma --env TAHOMA_TOKEN=<your-token> -- uv run --directory /path/to/mcp-tahoma mcp-tahoma
```

Or, in a client that reads a JSON config:

```json
{
  "mcpServers": {
    "tahoma": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/mcp-tahoma", "mcp-tahoma"],
      "env": { "TAHOMA_TOKEN": "<your-token>" }
    }
  }
}
```

The server speaks stdio by default; `--transport http --port 8765` serves HTTP
instead.

## Tools

One tool per local API operation, generated from `tahoma-openapi.yml`:

| Tool | Endpoint |
| ---- | -------- |
| `get_api_version` | `GET /apiVersion` |
| `get_setup` | `GET /setup` |
| `get_gateways` | `GET /setup/gateways` |
| `get_devices` | `GET /setup/devices` |
| `get_device` | `GET /setup/devices/{deviceURL}` |
| `get_device_states` | `GET /setup/devices/{deviceURL}/states` |
| `get_device_state` | `GET /setup/devices/{deviceURL}/states/{name}` |
| `get_devices_by_controllable` | `GET /setup/devices/controllables/{controllableName}` |
| `execute_actions` | `POST /exec/apply` |
| `get_current_executions` | `GET /exec/current` |
| `get_current_execution` | `GET /exec/current/{executionId}` |
| `cancel_all_executions` | `DELETE /exec/current/setup` |
| `cancel_execution` | `DELETE /exec/current/setup/{executionId}` |
| `register_event_listener` | `POST /events/register` |
| `fetch_events` | `POST /events/{listenerId}/fetch` |
| `unregister_event_listener` | `POST /events/{listenerId}/unregister` |

Plus two local helpers: `gateway_connection_info` (which box, which URL, TLS
status) and `discover_local_gateways` (mDNS browse).

Closing a shutter, for instance, is a single `execute_actions` call:

```json
{
  "label": "close the living room",
  "actions": [
    {
      "deviceURL": "io://2001-1234-5678/12345678",
      "commands": [{ "name": "setClosure", "parameters": [100] }]
    }
  ]
}
```

The commands a device accepts are listed in its `definition.commands`, returned
by `get_devices`.

## How the box is found

1. `TAHOMA_HOST` if set;
2. otherwise `gateway-<TAHOMA_PIN>.local` when `TAHOMA_PIN` is set and resolves;
3. otherwise an mDNS browse for `_kizboxdev._tcp`, matching the `gateway_pin`
   TXT record — via python-zeroconf, falling back to `dns-sd` (on macOS the
   system mDNS daemon regularly sees the box when python-zeroconf does not).

If the gateway is reached by name, the connection is fully verified; if only an
IP address is available, the certificate chain is still verified but the
hostname cannot be.

## TLS

The box serves a certificate signed by the self-signed Overkiz authority, so
`src/mcp_tahoma/certs/overkiz-root-ca-2048.crt` (from
<https://ca.overkiz.com/overkiz-root-ca-2048.crt>) is bundled and trusted for
this connection only — the system trust store is untouched.

`TAHOMA_VERIFY_SSL=false` disables verification entirely. It is a last resort:
the traffic stays encrypted but nothing proves you are talking to your box.

## Rate limits

There is no rate limiting on the local API, but the gateway is a small device.
Call `get_setup` once at start, then `register_event_listener` and poll
`fetch_events` at most once per second, rather than polling `get_devices`.
Listeners are destroyed after 10 minutes of inactivity or when the box reboots.

## Development

```sh
uv sync
uv run pytest
```

The tests cover the generated tools, discovery parsing and the TLS behaviour
against a throwaway HTTPS server presenting a gateway-like certificate; no real
box is needed.

`src/mcp_tahoma/tahoma-openapi.yml` is a symlink to the descriptor at the
repository root, so the spec has a single source of truth and still ships
inside the wheel.

## References

- [Somfy TaHoma Developer Mode](https://github.com/Somfy-Developer/Somfy-TaHoma-Developer-Mode)
  (vendored in `Somfy-TaHoma-Developer-Mode-main/`)