Skip to main content
Glama
Indigo MCP Server Plugin

Indigo MCP Server Plugin

by mlamoure
README.md
# MCP Advanced Tools for Indigo

Indigo 2026.1 has its own MCP server built in, [MCP Control](https://docs.indigodomo.com/2026.1/user/mcp-control/).
It lets an AI assistant control devices, set variables and run action groups. MCP Advanced Tools is a
second MCP server that runs next to it and covers what MCP Control leaves out:

- **Search everything**: natural-language search across devices, variables, action groups, triggers and
  schedules, including objects not published for remote display.
- **See inside automations**: the action steps and conditions of any trigger, schedule or action group,
  and which automations could change a given device or variable.
- **Investigate**: "what caused this?" for a device change, and search of the full event-log history.
- **Manage automations**: enable or disable (optionally for a set time), duplicate, move, delete, and edit
  names, descriptions and trigger events.
- **Tags**: create, rename, recolor and delete tags, and add or remove them on any object in bulk.
- **Event webhooks**: push a notification to your own server when a device or variable condition matches.
- **Plugin-provided tools**: tools that other Indigo plugins contribute.

Example questions:

- "Find all light switches in the bedroom"
- "What trigger turned off the porch light last night?"
- "What could change the hallway thermostat?"
- "Disable the motion trigger for two hours"
- "Tag every outdoor light and the schedules that run them as `outside`"

## Requirements

- **Indigo** 2026.1 or later. (On Indigo 2025.2, use MCP Server 2026.10.1.)
- **Apple Silicon** Mac.
- **OpenAI API key** for semantic search ([get one](https://platform.openai.com/api-keys)). See
  [Privacy & Security](#privacy--security) for what is sent.
- **Node.js** only for Claude Desktop, which connects through `npx mcp-remote`: `brew install node`.

## Installation

1. Install the **MCP Advanced Tools** plugin in Indigo.
2. Enter your **OpenAI API key** in the plugin's preferences.
3. Create one **MCP Advanced Tools** device. This starts the server.
4. Wait a minute or two while the plugin indexes your database.
5. Connect your AI client (below).

The **Model** preference (default `gpt-6-luna`) is the OpenAI model used to expand search queries.
Embeddings always use `text-embedding-3-large`.

## Connecting a client

Connect your AI client to **both** servers. Both run on the Indigo Web Server and accept the same key.

| Server | Endpoint path | Use it for |
|--------|---------------|------------|
| Indigo MCP Control | `/v3/api/mcp` | controlling devices, variables, running action groups/triggers/schedules, listing objects, plugins |
| MCP Advanced Tools | `/message/com.vtmikel.mcp_server/mcp/` | everything listed above |

**Key.** Send an Indigo API key or a local secret as `Authorization: Bearer <key>`. Create API keys under
[Authorizations in your Indigo Account](https://www.indigodomo.com/account/authorizations); local secrets go
in `Preferences/secrets.json` in your Indigo install folder (see Indigo's
[connection guide](https://docs.indigodomo.com/2026.1/user/mcp-control/connecting/)).

**Base URL.** Put the endpoint path after one of these (default port `8176`):

- Same Mac: `http://localhost:8176`
- Your LAN: `http://<indigo-ip>:8176`
- Remote: `https://<your-reflector>.indigodomo.net`

**VS Code, Cursor, Claude Code** (direct HTTP: `.vscode/mcp.json`, Cursor's MCP settings,
`~/.claude.json` or a project `.mcp.json`):

```json
{
  "mcpServers": {
    "indigo": {
      "type": "http",
      "url": "http://localhost:8176/v3/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    },
    "indigo-advanced-tools": {
      "type": "http",
      "url": "http://localhost:8176/message/com.vtmikel.mcp_server/mcp/",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}
```

**Claude Desktop** has no direct HTTP support, so it goes through `mcp-remote`
(`~/Library/Application Support/Claude/claude_desktop_config.json`). Add one entry per server:

```json
{
  "mcpServers": {
    "indigo-advanced-tools": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://your-reflector.indigodomo.net/message/com.vtmikel.mcp_server/mcp/",
        "--header", "Authorization:Bearer YOUR_API_KEY"
      ]
    }
  }
}
```

For plain HTTP on the LAN, add `"--allow-http"` to `args`. For a self-signed HTTPS certificate, add
`"env": { "NODE_TLS_REJECT_UNAUTHORIZED": "0" }` to the entry.

**Transport.** The endpoint speaks MCP streamable HTTP: `POST` only (`GET` returns `405`), no server push.
It serves protocol `2026-07-28` statelessly and the older session-based `2025-11-25` / `2025-06-18`
(sessions expire after 2 hours idle).

## How the two servers split the work

| You want to… | MCP Control | MCP Advanced Tools |
|--------------|-------------|--------------------|
| Control devices, set or create variables | ✓ | — |
| Run an action group, trigger or schedule | ✓ (with confirmation) | — |
| List and look up objects | ✓ published objects | triggers and schedules |
| Find things by meaning ("entryway light" → *Foyer Light*) | ✓ published objects | ✓ every object |
| See an automation's steps and conditions | ✗ | ✓ |
| "What could change this device?" / "What caused this?" | ✗ | ✓ |
| Search the event log | recent lines | ✓ full history |
| Enable/disable, duplicate, move, delete, rename automations | ✗ | ✓ |
| Read tags | ✓ | ✓ |
| Create, edit, delete and apply tags | ✗ | ✓ |
| Webhooks to your own server | ✗ | ✓ |
| Tools from other plugins | ✗ | ✓ |

- MCP Control only sees objects published for remote display. This plugin sees every object, so it can
  find a device that MCP Control can't control until you publish it.
- `list_triggers`, `list_schedules` and `query_event_log` exist on both servers. This plugin's versions add
  trigger summaries, next run times and full log history.
- Action steps, conditions and schedule timing can't be edited over MCP; change them in Indigo. To make a
  variant of an automation, duplicate it and edit the copy.

## Tools

Seventeen tools, three more when webhooks are on, plus any plugin-provided tools. List tools page with
`limit` (default 50, max 500) and `offset`.

### Search

- **search_entities**: natural-language search across every device, variable, action group, trigger and
  schedule. An exact name match comes first; otherwise results rank by name and meaning. Filter by
  `device_types`, `state_filter` and `tags` (`tag_match`: `all` or `any`). Returns 10 results unless you set
  `limit`.

### Automations

- **list_triggers** / **list_schedules**: one-line summaries (schedules include the next run time); filter by
  name, type, enabled, folder or tags.
- **get_trigger_details** / **get_schedule_details** / **get_action_group_details**: the full automation —
  event or timing, condition tree or condition script, and every action step, with IDs resolved to names.
- **find_automation_references**: which automations watch, act on, set or check a device, variable or action
  group, including through nested action groups and embedded Python.
- **control_trigger** / **control_schedule** / **control_action_group**: `enable` / `disable` (with
  `duration_seconds` to revert automatically, or `delay_seconds` to start later), `duplicate`,
  `move_to_folder`, `remove_delayed_actions`, `delete`. Action groups support duplicate, move and delete.
- **update_trigger**: edit name, description and the trigger's event (device/variable, comparison, value).
- **update_schedule** / **update_action_group**: edit name and description.

To run an automation, use MCP Control's `execute_trigger`, `execute_schedule` or `execute_action_group`.

### Investigation

- **investigate_event**: finds a device's state change in the log and ranks the automations that likely
  caused it.
- **query_event_log**: the event log, newest first. Without filters it reads the live log; with
  `query`, `regex`, `types`, `start_time` or `end_time` it searches the full history.

### Tags

- **manage_tags**: `create` (name and hex color), `update` (rename or recolor), `delete`. Names are
  lowercase words joined by dashes (`holiday-lighting`).
- **tag_objects**: `add` or `remove` one tag on up to 200 objects of any type (`device`, `variable`,
  `trigger`, `schedule`, `action_group`, `control_page`). Each object reports its outcome; `dry_run=true`
  previews.

### Event subscriptions (when webhooks are on)

- **create_event_subscription**, **list_event_subscriptions**, **delete_event_subscription**. See
  [Event webhooks](#event-webhooks).

### Safety settings

| Setting | Default | Effect |
|---------|---------|--------|
| Allow AI to delete automations | off | Required, with `confirm=true`, to delete a trigger, schedule or action group |
| Allow AI to delete tags | off | Required, with `confirm=true`, to delete a tag |
| Confirmation tag | `ask-first` | Automations carrying this tag need `confirm=true` for any change. Empty turns it off |
| Allow plugin-provided tools to make changes | on | Off makes plugin-provided write tools refuse |

Removing a tag from more than 25 objects, or removing or renaming the confirmation tag, also needs
`confirm=true`.

## Plugin-provided tools

Other Indigo plugins can add tools to this server. The plugin finds them automatically and lists them under
the provider's prefix, for example `autolights_update_zone` from
[Auto Lights](https://github.com/mlamoure/indigo-auto-lights).

- **Which plugins**: the preference *Plugins that may provide MCP tools* is **All plugins** (default),
  **Only the plugins selected below**, or **All plugins except those selected below**. An excluded plugin's
  tools are not listed or called.
- **Refresh**: new tools appear within a minute. **Plugins → MCP Advanced Tools → Rescan Plugin-Provided MCP
  Tools** forces it. A connected client may need a new session to see them.

### Add MCP tools to your plugin

Full rules: [docs/mcp-provider-manifest.md](docs/mcp-provider-manifest.md).

1. Ship `Contents/Resources/mcp-manifest.json`:

   ```json
   {
     "manifest_version": 1,
     "provider": {"plugin_id": "com.example.myplugin", "display_name": "My Plugin"},
     "tools": [
       {
         "name": "get_status",
         "description": "Return the plugin's current status.",
         "write": false,
         "inputSchema": {"type": "object", "properties": {}, "required": []}
       }
     ]
   }
   ```

2. Declare a hidden action in `Actions.xml` and handle it in `plugin.py`:

   ```xml
   <Action id="mcp_tool_invoke" uiPath="hidden">
       <Name>MCP Tool Invocation Endpoint</Name>
       <CallbackMethod>handle_mcp_tool_invoke</CallbackMethod>
   </Action>
   ```

   ```python
   def handle_mcp_tool_invoke(self, action, dev=None, caller_waiting_for_result=True):
       import json
       tool = action.props.get("tool", "")
       args = json.loads(action.props.get("arguments", "{}"))
       if tool == "get_status":
           return json.dumps({"status": "ok", "result": {"state": "running"}})
       return json.dumps({"status": "error",
                          "error": {"type": "not_found", "message": f"unknown tool {tool}"}})
   ```

3. Optionally, announce your tools from `startup()` so they register immediately:

   ```python
   try:
       indigo.server.broadcastToSubscribers("mcp_tools_updated")
   except Exception:
       pass
   ```

Your plugin works the same without MCP Advanced Tools installed.

## Event webhooks

An event subscription sends an HTTP POST to **your own server** when a device or variable condition
matches — "the next time the front door opens", "if the garage stays open for 10 minutes".

> The plugin only sends. It does not work with stock Claude Desktop or other clients that can't receive
> a webhook; it is meant for agents and automation systems with their own HTTP endpoint.

Turn it on in **Plugins → MCP Advanced Tools → Configure → Enable Event Webhooks**.

### Creating a subscription

| Parameter | Required | Description |
|-----------|----------|-------------|
| `webhook_url` | yes | Your endpoint |
| `entity_type` | yes | `"device"` or `"variable"` |
| `conditions` | yes | State conditions, combined with AND |
| `entity_id` | no | One device/variable; omit to watch all of that type |
| `auth` | no | `{ "mode": "none"\|"bearer"\|"hmac", "token": "…", "verify_ssl": true }` |
| `duration_seconds` | no | Condition must hold this long before firing |
| `max_fires` | no | Delete after this many deliveries (`1` = one-shot) |
| `description` | no | Label |

A subscription fires when the state *changes into* a match, not repeatedly while it stays matched.

```python
create_event_subscription(
    webhook_url="https://my-server.example.com/indigo-hook",
    entity_type="device", entity_id=67890,
    conditions={"onState": True}, duration_seconds=600,
    description="Garage left open",
)
```

### Conditions

Match any state key, including plugin states, by value or with an operator:

```jsonc
{ "onState": true }
{ "temperatureInput1": { "gt": 80 }, "onState": true }
```

Operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `contains`, `regex`. Variables match on `value`; booleans and
numbers are compared as such. `{ "any_change": true }` fires on every variable change (not combinable with
`duration_seconds`).

### Authentication

- `bearer`: sends `Authorization: Bearer <token>`.
- `hmac`: sends `X-Webhook-Signature: sha256=<HMAC-SHA256(token, body)>` and `X-Webhook-Timestamp`.

  ```python
  import hmac, hashlib
  expected = "sha256=" + hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
  ok = hmac.compare_digest(expected, request.headers["X-Webhook-Signature"])
  ```

Set `"verify_ssl": false` only for a self-signed receiver.

### Payload

A JSON `POST` with headers `X-Event-Id`, `X-Event-Type` and `X-Subscription-Id`:

```json
{
  "event_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "schema_version": "1.0",
  "dedupe_key": "indigo:device:12345:state:onState:True",
  "source": { "system": "indigo", "plugin": "com.vtmikel.mcp_server", "host": "my-indigo-mac" },
  "timestamp": "2026-06-01T15:30:45.123456+00:00",
  "event_type": "device.state_changed",
  "entity": { "kind": "device", "id": 12345, "name": "Front Door", "device_type": "…" },
  "state": { "changed_keys": ["onState"], "old": { "onState": false }, "new": { "onState": true } },
  "trigger": { "subscription_id": "…", "conditions_matched": { "onState": true } },
  "human": { "title": "Front Door state changed", "summary": "Front Door: onState=true" }
}
```

Variable events use `event_type: "variable.value_changed"` and `entity.kind: "variable"`.

### Delivery

- **At least once**: deduplicate by `event_id`. Respond with any `2xx`.
- **Retries**: up to 4 attempts with backoff on `5xx` and network errors; a `4xx` is not retried.
- **Persistence**: subscriptions survive restarts. They are stored, with their auth tokens, in
  `…/Preferences/Plugins/com.vtmikel.mcp_server/subscriptions.json` (mode `0600`). A pending
  `duration_seconds` wait does not survive a restart.

### Managing subscriptions in a browser

When webhooks are on, `http://<indigo-host>:8176/message/com.vtmikel.mcp_server/events_ui/` lists active
subscriptions and lets you remove them. It uses normal Indigo Web Server login. **Plugins → MCP Advanced
Tools → Print Event Subscriptions Web UI URL** logs the URLs.

![Event Subscriptions web UI](docs/event-subscriptions-web-ui.png)

## Tips

- Include the location and device type in a question.
- Device Notes are part of the search index, so good notes improve results.

## Privacy & Security

**Sent to OpenAI** to build the search index (stored on your Indigo Mac): names, descriptions, models and
types of devices, variables, action groups, triggers and schedules, plus your search queries. **Never
sent:** device states or values, addresses, credentials, URLs, network configuration or history.

Every MCP request needs a key. Use the Indigo Reflector for encrypted remote access.

## Support

- **Issues:** the project repository
- **Questions:** [Indigo Domotics Forum](https://forums.indigodomo.com/viewforum.php?f=274)