Skip to main content
Glama
chllhc-SVG

Home Assistant MCP Control

by chllhc-SVG
README.md
# Home Assistant MCP Control

一个用于控制外部 Home Assistant 设备的 MCP 控制层与前端系统页面。

## 项目定位

本仓库不是 Home Assistant 本体,也不是通用的自动化平台,而是一个**围绕你自己的 Home Assistant 实例**构建的控制与观测层。它的目标是把“设备发现、能力映射、MCP 工具暴露、审计日志、前端管理页面”串成一条稳定链路,方便大模型、前端页面和人工运维统一调用。

## 架构总览

整体架构按职责拆分为七层,彼此之间尽量保持单向依赖:

### 1. 启动装配层

负责进程启动、环境变量加载、运行模式选择和服务编排。

- 入口位于 `packages/mcp-server/src/main.ts`
- 支持 `stdio` 与 `http` 两种 MCP 运行模式
- 负责同时启动:
  - 管理 API(默认 `4000`)
  - MCP HTTP 服务(默认 `4010`)
- 负责把 runtime、工具集合、设备注册表、Home Assistant 客户端装配到 HTTP 或 stdio 入口中

### 2. 配置与设备映射层

负责把静态配置和 Home Assistant 动态实体融合成可控制设备视图。

- 配置文件:`config/lights.json`
- 作用:维护可控设备白名单和历史命名映射
- 设备映射会结合 Home Assistant 实体信息、房间信息、能力信息生成统一的 `DeviceRecord`
- 这层的目标不是“直接控制设备”,而是产出稳定、适合上层使用的设备视图

### 3. Schema 与策略校验层

负责对外部输入做结构化约束、字段校验和动作合法性检查。

- 入口文件:`packages/mcp-server/src/models/schemas.ts`
- 使用 `zod` 对所有工具参数进行强校验
- 负责把自然语言解析后的参数,转换成可执行、可验证的结构化输入
- 典型职责包括:
  - `entity_id` 非空校验
  - 亮度范围归一化
  - `hvac_mode` 枚举限制
  - 数值型参数的有限性校验

### 4. 工具层

负责把 schema 校验后的输入转成具体控制逻辑。

- 入口文件:`packages/mcp-server/src/tools/*`
- 负责设备发现、状态查询、开关控制、亮度控制、数值控制、空调控制等
- 工具层面向的是“业务动作”,而不是 HTTP 或 MCP 协议

### 5. Home Assistant 访问层

负责与真实 Home Assistant 实例通信。

- 入口文件:`packages/mcp-server/src/services/ha-client.ts`
- 负责读取 Home Assistant 实体、查询状态、下发控制指令
- 处理鉴权、超时、错误转换、实体发现等能力
- 上层只看到统一错误格式,不直接处理底层 API 细节

### 6. 对外服务层

负责把同一套 runtime 暴露成 HTTP 管理 API 与 MCP 服务。

- HTTP 管理 API:`packages/mcp-server/src/server.ts`
- MCP JSON-RPC / HTTP:`packages/mcp-server/src/mcp-http.ts`
- stdio MCP:`packages/mcp-server/src/mcp.ts`

这层主要解决三件事:

- 给前端页面提供数据接口
- 给 MCP 客户端提供工具注册与调用接口
- 保证不同传输方式下工具定义一致

### 7. 审计与记录层

负责记录每次工具调用、失败信息与统计数据。

- 入口文件:`packages/mcp-server/src/services/audit-store.ts`
- 记录请求时间、工具名、设备名、结果状态、错误码、耗时等字段
- 前端的“日志查询”和“失败统计”均依赖这一层

---

## 运行时数据流

一次典型的控制请求大致会经过以下链路:

1. 前端或 MCP 客户端发起请求
2. 对外服务层接收请求
3. Schema 层校验参数
4. 工具层决定具体动作
5. Home Assistant 访问层执行真实控制
6. 审计层记录结果
7. 前端或客户端拿到统一结果格式

这个链路的设计目标是:

- 输入可验证
- 控制可追踪
- 错误可定位
- HTTP 与 MCP 共用同一套底层能力

## MCP 接入方式

### stdio 模式

适合本地 MCP 客户端直接启动进程连接。

### HTTP 模式

适合通过远程 URL 访问 MCP。

默认对外地址:

- `http://127.0.0.1:4010/mcp`

兼容的消息入口:

- `POST /mcp`
- `POST /mcp/messages`
- `GET /mcp` 用于 SSE 连接和会话协商

## 管理 API

默认管理 API 运行在:

- `http://127.0.0.1:4000`

常用接口:

- `GET /healthz`
- `GET /api/admin/stats/overview`
- `GET /api/admin/stats/errors`
- `GET /api/admin/logs`
- `GET /api/admin/devices`
- `GET /api/admin/ha/entities/discover`

## Schema 设计说明

这一层的原则是:**外部请求尽量宽松,内部执行尽量严格**。

- MCP 工具会接收自然语言拆解后的参数
- 进入控制层之前必须经过 schema 校验
- 校验后的数据再交给工具层和 Home Assistant 访问层

### 通用字段规范

- `entity_id`
  - 类型:`string`
  - 要求:非空
  - 含义:Home Assistant 实体标识,例如 `light.living_room_main`

- `query`
  - 类型:`string`
  - 要求:非空
  - 含义:自然语言设备描述,例如“打开客厅灯”

- `domain`
  - 类型:`string`
  - 要求:可选
  - 含义:实体域,例如 `light`、`switch`、`climate`

- `room`
  - 类型:`string`
  - 要求:可选
  - 含义:房间/区域过滤条件

- `keyword`
  - 类型:`string`
  - 要求:可选
  - 含义:模糊搜索关键字

- `enabled_only`
  - 类型:`boolean`
  - 要求:可选
  - 含义:是否只返回启用设备

## 具体 Schema 参数结构

以下内容对应 `packages/mcp-server/src/models/schemas.ts` 的实际定义。

### 1. `listDevicesInputSchema`

用于列出可控制设备。

```ts
{
  domain?: string;
  room?: string;
  keyword?: string;
  enabled_only?: boolean;
}
```

#### 字段说明

- `domain`
  - 可选
  - 设备域过滤,例如 `light`、`switch`、`button`、`number`、`climate`、`sensor`
- `room`
  - 可选
  - 房间过滤,例如 `living_room`
- `keyword`
  - 可选
  - 根据显示名、别名或实体特征做模糊筛选
- `enabled_only`
  - 可选
  - `true` 表示只返回启用设备;未传或 `false` 时返回所有匹配项

---

### 2. `resolveDeviceInputSchema`

用于把自然语言请求解析为设备候选。

```ts
{
  query: string;
  domain?: string;
  room?: string;
}
```

#### 字段说明

- `query`
  - 必填
  - 最小长度 1
  - 例如:`打开客厅灯`
- `domain`
  - 可选
  - 进一步缩小解析范围
- `room`
  - 可选
  - 进一步限定房间范围

#### 返回场景

此 schema 常用于“智能匹配候选设备”,不是直接控制设备。

---

### 3. `getDeviceStateInputSchema`

用于查询某个设备当前状态。

```ts
{
  entity_id: string;
}
```

#### 字段说明

- `entity_id`
  - 必填
  - 最小长度 1
  - 例如:`switch.kitchen_main`

---

### 4. `controlDeviceInputSchema`

用于统一控制多种设备类型。

```ts
{
  entity_id: string;
  action:
    | 'turn_on'
    | 'turn_off'
    | 'press'
    | 'set_brightness'
    | 'set_value'
    | 'set_temperature'
    | 'set_hvac_mode'
    | 'set_fan_mode'
    | 'set_swing_mode';
  brightness?: number;
  value?: number;
  temperature?: number;
  hvac_mode?: 'off' | 'heat' | 'cool' | 'heat_cool' | 'auto' | 'dry' | 'fan_only';
  fan_mode?: string;
  swing_mode?: string;
}
```

#### 字段说明

- `entity_id`
  - 必填
  - 目标实体 ID
- `action`
  - 必填
  - 控制动作类型
- `brightness`
  - 可选
  - 当 `action = set_brightness` 时使用
  - 亮度值通常应在 `0 ~ 255` 之间,前端会做滑块控制
- `value`
  - 可选
  - 当 `action = set_value` 时使用
  - 用于 `number` 域
- `temperature`
  - 可选
  - 当 `action = set_temperature` 时使用
  - 用于 `climate` 域
- `hvac_mode`
  - 可选
  - 当 `action = set_hvac_mode` 时使用
  - 仅允许以下值:`off`、`heat`、`cool`、`heat_cool`、`auto`、`dry`、`fan_only`
- `fan_mode`
  - 可选
  - 当 `action = set_fan_mode` 时使用
- `swing_mode`
  - 可选
  - 当 `action = set_swing_mode` 时使用

#### 动作与参数关系

- `turn_on`
  - 只需要 `entity_id`
- `turn_off`
  - 只需要 `entity_id`
- `press`
  - 只需要 `entity_id`
- `set_brightness`
  - 需要 `entity_id` + `brightness`
- `set_value`
  - 需要 `entity_id` + `value`
- `set_temperature`
  - 需要 `entity_id` + `temperature`
- `set_hvac_mode`
  - 需要 `entity_id` + `hvac_mode`
- `set_fan_mode`
  - 需要 `entity_id` + `fan_mode`
- `set_swing_mode`
  - 需要 `entity_id` + `swing_mode`

---

### 5. `listLightsInputSchema`

用于列出灯光设备。

```ts
{
  room?: string;
  keyword?: string;
  support_brightness?: boolean;
}
```

#### 字段说明

- `room`
  - 可选
- `keyword`
  - 可选
- `support_brightness`
  - 可选
  - 是否只看支持亮度的灯

---

### 6. `resolveLightInputSchema`

用于自然语言解析灯光目标。

```ts
{
  query: string;
}
```

#### 字段说明

- `query`
  - 必填
  - 例如:`打开卧室灯`

---

### 7. `getLightStateInputSchema`

用于查询灯光状态。

```ts
{
  entity_id: string;
}
```

---

### 8. `turnOnLightInputSchema`

```ts
{
  entity_id: string;
}
```

---

### 9. `turnOffLightInputSchema`

```ts
{
  entity_id: string;
}
```

---

### 10. `setLightBrightnessInputSchema`

用于设置灯光亮度。

```ts
{
  entity_id: string;
  brightness: number;
}
```

#### 约束

- `brightness` 会先转换为数字
- 会四舍五入为整数
- 最终必须在 `0 ~ 255` 之间

---

### 11. `setLightStateInputSchema`

用于同时设置灯光开关状态与可选亮度。

```ts
{
  entity_id: string;
  state: 'on' | 'off';
  brightness?: number;
}
```

#### 字段说明

- `state`
  - 必填
  - 仅允许 `on` 或 `off`
- `brightness`
  - 可选
  - 一般与 `state = on` 联合使用

---

### 12. `pressButtonInputSchema`

```ts
{
  entity_id: string;
}
```

用于按钮类实体执行一次按下动作。

---

### 13. `setNumberValueInputSchema`

```ts
{
  entity_id: string;
  value: number;
}
```

用于数值类实体设置目标值。

---

### 14. `turnOnSwitchInputSchema`

```ts
{
  entity_id: string;
}
```

---

### 15. `turnOffSwitchInputSchema`

```ts
{
  entity_id: string;
}
```

---

### 16. `listClimateDevicesInputSchema`

用于列出空调/气候类设备。

```ts
{
  room?: string;
  keyword?: string;
}
```

---

### 17. `setClimateTemperatureInputSchema`

```ts
{
  entity_id: string;
  temperature: number;
}
```

用于设置气候设备目标温度。

---

### 18. `setClimateHvacModeInputSchema`

```ts
{
  entity_id: string;
  hvac_mode: 'off' | 'heat' | 'cool' | 'heat_cool' | 'auto' | 'dry' | 'fan_only';
}
```

#### 约束

- `hvac_mode` 是枚举值,必须是 Home Assistant 支持的 HVAC 模式之一
- 不允许传入任意字符串

---

### 19. `setClimateFanModeInputSchema`

```ts
{
  entity_id: string;
  fan_mode: string;
}
```

#### 字段说明

- `fan_mode`
  - 必填
  - 非空字符串
  - 具体可用值由目标设备自身支持的模式决定

---

### 20. `setClimateSwingModeInputSchema`

```ts
{
  entity_id: string;
  swing_mode: string;
}
```

#### 字段说明

- `swing_mode`
  - 必填
  - 非空字符串
  - 具体可用值由目标设备自身支持的模式决定

## 设备域支持说明

当前支持的设备域:

- `light`:开关、亮度、状态查询
- `switch`:开关、状态查询
- `button`:按下、状态查询
- `number`:数值设置、状态查询
- `climate`:温度、HVAC 模式、风扇模式、摆风模式、状态查询
- `sensor`:状态查询

## 常用命令

```bash
pnpm docker:dev
docker compose up -d --build
docker compose up -d
docker compose ps
docker compose logs -f mcp-server
docker compose logs -f log-platform
docker compose down
```

## 目录说明

- `packages/mcp-server`:MCP 控制层后端
- `apps/log-platform`:前端系统页面
- `config/lights.json`:可控制设备白名单,历史命名保留,内容已支持多设备域
- `start.bat` / `start.ps1`:本地一键启动脚本,内部已切换为跨平台逻辑