initial commit

This commit is contained in:
Martin
2026-02-10 13:24:32 -05:00
commit 66bce7b869
8 changed files with 639 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
# MCP Bridge - Home Assistant Custom Integration
This integration provides services to expose filtered entities and custom scripts to MCP (Model Context Protocol) servers, enabling AI agents to control your Home Assistant instance.
## Installation
### Via HACS (Recommended)
1. **Install HACS** (if not already installed): https://hacs.xyz/docs/setup/download
2. **Add this repository as a custom repository:**
- Open HACS in Home Assistant
- Click the three dots in the top right
- Select "Custom repositories"
- Add your Gitea URL: `http://your-gitea-server/username/homeassistant-mcp-bridge`
- Category: Integration
- Click "Add"
3. **Install the integration:**
- Go to HACS → Integrations
- Click "+ Explore & Download Repositories"
- Search for "MCP Bridge"
- Click "Download"
- Restart Home Assistant
### Manual Installation
1. Copy the `mcp_bridge` folder to your Home Assistant `custom_components` directory:
```
/config/custom_components/mcp_bridge/
```
2. Restart Home Assistant
3. The integration will be automatically loaded (no configuration needed)
## Services
### `mcp_bridge.get_exposed_entities`
Returns all entities that are exposed to voice assistants.
**Returns:**
```json
{
"entities": [...],
"count": 42
}
```
### `mcp_bridge.get_exposed_scripts`
Returns all custom scripts marked with the `mcp_accessible` label.
**Returns:**
```json
{
"scripts": [...],
"count": 1
}
```
### `mcp_bridge.get_entity_metadata`
Get detailed metadata for a specific entity.
**Parameters:**
- `entity_id` (required): The entity to query
**Returns:**
```json
{
"entity_id": "light.living_room",
"state": "on",
...
}
```
## Usage
### Exposing Entities to AI Agent
1. Go to Settings → Voice Assistants → Expose
2. Toggle entities you want the AI agent to control
3. These entities will be returned by `mcp_bridge.get_exposed_entities`
### Exposing Scripts to AI Agent
1. Create your custom script in HA
2. Add the label `mcp_accessible` to the script:
- Go to Settings → Automations & Scenes → Scripts
- Click on your script
- Click the settings icon (top right)
- Under "Labels", add `mcp_accessible`
3. The script will now be returned by `mcp_bridge.get_exposed_scripts`
## Example: Marking a Script as MCP-Accessible
```yaml
# In your scripts.yaml or via UI
set_master_bedroom_fan_speed:
alias: "Set Master Bedroom Fan Speed"
description: "[MCP] Set the speed of the master bedroom fan."
# Add label via UI: Settings → Scripts → (your script) → Labels → mcp_accessible
fields:
fan_state:
name: Fan speed
description: "0=off, 1=low, 2=medium, 3=high"
required: true
selector:
number:
min: 0
max: 3
step: 1
sequence:
# Your script actions here
```
## Testing
You can test the services in Home Assistant's Developer Tools → Services:
1. Select `mcp_bridge.get_exposed_entities`
2. Click "Call Service"
3. View the response in the "Response" tab
## Troubleshooting
**No entities returned:**
- Make sure you've exposed entities via Settings → Voice Assistants → Expose
**No scripts returned:**
- Ensure your scripts have the `mcp_accessible` label
- Check Home Assistant logs for errors: Settings → System → Logs
**Integration not loading:**
- Check the logs for errors
- Ensure the folder structure is correct
- Restart Home Assistant
## Version
0.1.0 - Initial release

View File

@@ -0,0 +1,171 @@
"""MCP Bridge integration for Home Assistant.
This integration provides services to expose filtered entities and scripts
to MCP (Model Context Protocol) servers for AI agent control.
"""
import logging
from typing import Any
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.typing import ConfigType
from homeassistant.const import CONF_ENTITY_ID
_LOGGER = logging.getLogger(__name__)
DOMAIN = "mcp_bridge"
# Label to mark scripts as MCP-accessible
MCP_ACCESSIBLE_LABEL = "mcp_accessible"
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the MCP Bridge integration."""
_LOGGER.info("Setting up MCP Bridge integration")
async def get_exposed_entities(call: ServiceCall) -> dict[str, Any]:
"""Get all entities exposed to voice assistants."""
entity_reg = er.async_get(hass)
exposed_entities = []
# Iterate through all entities
for entity_id, state in hass.states.async_all():
entity_entry = entity_reg.async_get(entity_id)
# Check if entity is exposed to conversation (voice assistant)
if entity_entry and entity_entry.options.get("conversation", {}).get("should_expose", False):
# Get area name if available
area_name = None
if entity_entry.area_id:
area_reg = hass.helpers.area_registry.async_get(hass)
area = area_reg.async_get_area(entity_entry.area_id)
if area:
area_name = area.name
# Build entity data
entity_data = {
"entity_id": entity_id,
"state": state.state,
"friendly_name": state.attributes.get("friendly_name", entity_id),
"area": area_name,
"domain": entity_id.split(".")[0],
"device_class": state.attributes.get("device_class"),
"supported_features": state.attributes.get("supported_features", 0),
"last_changed": state.last_changed.isoformat(),
"last_updated": state.last_updated.isoformat(),
"attributes": dict(state.attributes)
}
exposed_entities.append(entity_data)
_LOGGER.debug(f"Found {len(exposed_entities)} exposed entities")
return {
"entities": exposed_entities,
"count": len(exposed_entities)
}
async def get_exposed_scripts(call: ServiceCall) -> dict[str, Any]:
"""Get all scripts marked as MCP-accessible."""
entity_reg = er.async_get(hass)
exposed_scripts = []
# Get all script entities
for entity_id in hass.states.async_entity_ids("script"):
entity_entry = entity_reg.async_get(entity_id)
# Check if script has the mcp_accessible label
if entity_entry and MCP_ACCESSIBLE_LABEL in entity_entry.labels:
state = hass.states.get(entity_id)
if not state:
continue
# Get script attributes
script_data = {
"entity_id": entity_id,
"friendly_name": state.attributes.get("friendly_name", entity_id),
"description": state.attributes.get("description", ""),
"fields": state.attributes.get("fields", {}),
}
exposed_scripts.append(script_data)
_LOGGER.debug(f"Found {len(exposed_scripts)} exposed scripts")
return {
"scripts": exposed_scripts,
"count": len(exposed_scripts)
}
async def get_entity_metadata(call: ServiceCall) -> dict[str, Any]:
"""Get detailed metadata for a specific entity."""
entity_id = call.data.get(CONF_ENTITY_ID)
if not entity_id:
_LOGGER.error("entity_id not provided")
return {"error": "entity_id required"}
entity_reg = er.async_get(hass)
entity_entry = entity_reg.async_get(entity_id)
state = hass.states.get(entity_id)
if not state:
_LOGGER.error(f"Entity {entity_id} not found")
return {"error": f"Entity {entity_id} not found"}
# Get area name if available
area_name = None
if entity_entry and entity_entry.area_id:
area_reg = hass.helpers.area_registry.async_get(hass)
area = area_reg.async_get_area(entity_entry.area_id)
if area:
area_name = area.name
metadata = {
"entity_id": entity_id,
"state": state.state,
"friendly_name": state.attributes.get("friendly_name", entity_id),
"area": area_name,
"domain": entity_id.split(".")[0],
"device_class": state.attributes.get("device_class"),
"supported_features": state.attributes.get("supported_features", 0),
"last_changed": state.last_changed.isoformat(),
"last_updated": state.last_updated.isoformat(),
"attributes": dict(state.attributes)
}
if entity_entry:
metadata["labels"] = list(entity_entry.labels)
metadata["is_exposed_to_conversation"] = entity_entry.options.get(
"conversation", {}
).get("should_expose", False)
return metadata
# Register services
hass.services.async_register(
DOMAIN,
"get_exposed_entities",
get_exposed_entities,
schema=None
)
hass.services.async_register(
DOMAIN,
"get_exposed_scripts",
get_exposed_scripts,
schema=None
)
hass.services.async_register(
DOMAIN,
"get_entity_metadata",
get_entity_metadata,
schema=None
)
_LOGGER.info("MCP Bridge services registered successfully")
return True

View File

@@ -0,0 +1,11 @@
{
"domain": "mcp_bridge",
"name": "MCP Bridge",
"documentation": "https://www.git.quarantinedstudio.com/mvezina/homeassistant-mcp-bridge",
"issue_tracker": "https://www.git.quarantinedstudio.com/mvezina/homeassistant-mcp-bridge/issues",
"requirements": [],
"codeowners": [],
"version": "0.1.0",
"iot_class": "local_polling",
"config_flow": false
}

View File

@@ -0,0 +1,21 @@
get_exposed_entities:
name: Get Exposed Entities
description: Returns all entities exposed to voice assistants with full metadata.
fields: {}
get_exposed_scripts:
name: Get Exposed Scripts
description: Returns all custom scripts marked as MCP-accessible (with mcp_accessible label).
fields: {}
get_entity_metadata:
name: Get Entity Metadata
description: Get detailed metadata for a specific entity.
fields:
entity_id:
name: Entity ID
description: The entity ID to get metadata for
required: true
example: "light.living_room"
selector:
entity: