内容
# Loxone MCP 代理
一个小型的**零依赖**本地代理,使 Claude Desktop(或任何基于 stdio 的 MCP 客户端)能够与 **Loxone Miniserver 的原生 MCP 服务器**建立**稳定的**连接,并实现**无头、基于凭证的登录**(无需浏览器,无需手动 OAuth 点击)。
> Loxone Gen 2 Miniservers(固件 **17.1+**)可以运行一个内置的 **MCP 服务器**插件。连接到它通常有两个痛点,而这个代理可以为您解决这些问题。
## 为什么存在这个代理
1. **Miniserver 的 MCP URL 不稳定。** Loxone 通过云“连接”服务发布端点,该服务**重定向到一个随时间变化的中继地址**(在 Miniserver 重启和中继重新洗牌时发生变化)。如果您将重定向的 `…dyndns.loxonecloud.com:PORT/mcp` URL 硬编码到客户端中,它将在稍后时间点失效。
2. **身份验证默认是交互式的。** MCP 端点使用 OAuth 2.1,官方流程会弹出一个浏览器登录页面。对于始终在线的客户端来说,这很不方便,对于无头设置来说更是无法实现。
**这个代理**向 Claude 公开一个普通的本地 **stdio** MCP 服务器,在另一侧,它:
- 解析**稳定的** `connect.loxonecloud.com/<serial>/mcp` 入口点到**当前**的中继,并**自动重新解析**,无论中继何时移动或死亡(因此您的 Claude 连接保持正常工作);
- 使用本地 `.env` 中的 Loxone 用户名/密码**无头执行 OAuth 登录**(直接驱动 Miniserver 的 HTML 登录表单 - 无需浏览器),然后缓存和刷新令牌;
- **透明地**代理 MCP 协议(可流式 HTTP ⇄ stdio)。
## 功能
- 🔌 **稳定** - 在自动重新解析后面隐藏旋转的中继地址。
- 🤖 **无头** - 从 `.env` 凭证登录,无需浏览器,无需点击。
- ♻️ **自我修复** - 在失败时重新解析中继,刷新/重新颁发令牌,并透明地重新初始化一个断开的会话。
- 🪶 **零运行时依赖** - 普通 Node.js(≥ 20)。无需构建。
- 🔒 **最小权限友好** - 登录为专用、受限的 Loxone 用户;令牌缓存具有 `0600` 权限。
## 工作原理
```
Claude Desktop ──stdio(JSON-RPC)──▶ loxone-mcp-proxy ──HTTPS(streamable MCP)──▶ Loxone 中继 ──▶ Miniserver
│
1) GET connect.loxonecloud.com/<serial>/mcp → 307 → 当前中继 "entry"
2) 在 entry 上进行无头 OAuth:
注册客户端 → GET authorize (HTML 登录表单)
→ POST 用户名+密码 (+隐藏字段) → ?code=…
→ 交换代码 (PKCE) → 访问 + 刷新令牌
3) 使用 Bearer 令牌与 entry 进行 MCP 通信
4) 在中继死亡 / 401 / 会话丢失 → 重新解析 / 刷新 / 重新初始化
```
一个微妙但重要的细节:服务器宣传的 OAuth **`resource`** 仅是令牌**受众标识符** - 它经常指向一个**不同的,有时无法访问**的中继。因此,MCP 流量被发送到**可达的已连接解析入口**,而不是 `resource`。代理为您处理此问题。
## 要求
- **Node.js ≥ 20** (使用内置的 `fetch`,无需安装包)。
- 一个 **Loxone Gen 2 Miniserver,固件 17.1+**,带有 **MCP 服务器**插件添加并运行(Loxone Config → 网络外设 → *MCP 服务器*)。
- 一个 **Loxone 用户账户** 用于助手。创建一个 **专用**账户,具有所需的最低权限(理想情况下为只读)。
## 设置
### 1. 获取代码
```bash
git clone https://github.com/ivantichy/loxone-mcp-proxy.git
cd loxone-mcp-proxy
# 无需 "npm install" - 无依赖
```
### 2. 配置凭证
```bash
cp .env.example .env
```
编辑 `.env`:
```ini
LOXONE_SERIAL=504F94A1AE7A # 您的 Miniserver 序列号(MAC,无冒号)
LOXONE_USER=mcp # 一个专用、最小权限的 Loxone 用户
LOXONE_PASSWORD=您的密码
```
**查找您的序列号:** 在浏览器中打开 `http://<miniserver-ip>/jdev/cfg/api`,并读取 `snr` 字段(例如 `50:4F:94:A1:AE:7A` → `504F94A1AE7A`),或在 Loxone Config 中读取。
### 3. 验证它是否有效
```bash
npm run login-check
```
预期结果:
```
OK 已认证并连接。 服务器:MCP Server 1.0.0
OK 11 个工具可用:control_find, control_command, system_status, ...
一切正常 - 此配置在 Claude Desktop 中有效。
```
如果失败,请参阅 [故障排除](#故障排除)。
### 4. 添加到 Claude Desktop
打开 **设置 → 开发者 → 编辑配置**,并添加一个通过 stdio 运行此代理的服务器。将其指向 `src/index.js`,使用绝对路径:
```jsonc
{
"mcpServers": {
"loxone": {
"command": "node",
"args": ["/绝对路径/to/loxone-mcp-proxy/src/index.js"]
}
}
}
```
在 **Windows** 上,请转义反斜杠:
```jsonc
{
"mcpServers": {
"loxone": {
"command": "node",
"args": ["C:\\路径\\到\\loxone-mcp-proxy\\src\\index.js"]
}
}
}
```
凭证从项目的 `.env` 读取,因此您无需在 Claude 配置中输入机密。**完全退出并重新打开** Claude Desktop,然后询问类似 *"Loxone 上有什么?"*。
## Claude 可以做什么
工具来自您的 Miniserver 的 MCP 插件,并使用 **您的 Loxone 用户权限** 运行。典型集合:
| 工具 | 类型 | 目的 |
|------|------|---------|
| `control_find`, `control_describe` | 读取 | 发现控件、房间、类别 |
| `control_state`, `system_status` | 读取 | 读取当前值/安装状态 |
| `control_history`, `control_statistics` | 读取 | 历史数据和聚合 |
| `weather` | 读取 | Loxone 天气数据 |
| `control_command`, `control_secured_command` | 写入 | 操作控件(灯、百叶窗、场景...) |
| `code_run`, `sdk_describe` | 高级 | 脚本访问(谨慎使用) |
要保持助手 **只读**,请限制 Loxone 用户的权限或在 MCP 客户端的连接器设置中禁用写入工具。
## 安全
- **使用专用 Loxone 用户**,具有必要的最小权限。助手只能看到和执行该用户可以执行的操作。
- 凭证仅存储在您的本地 `.env` 中(git-ignored)。令牌缓存在 `~/.loxone-mcp-proxy/cache.json` 中,具有 `0600` 权限。
- 除了 Loxone 自己的连接/中继基础设施和您的 Miniserver 外,**不会将任何内容发送到任何第三方云**。
## 故障排除
使用详细日志记录(输出到 stderr)运行:
```bash
LOXONE_DEBUG=1 npm run login-check
```
- **`登录被拒绝(表单重新渲染)`** - 错误的 `LOXONE_USER` / `LOXONE_PASSWORD`,或该用户可能无法访问此 Miniserver。
- **`连接受到速率限制(HTTP 429)`** - 连接端点每隔一段时间仅允许少数请求;等待一分钟。正常操作会缓存端点,很少命中它。
- **`经过 N 次尝试后无法认证`** - 检查 MCP 服务器插件是否已添加并在 Loxone Config 中运行,并且 Miniserver 是否具有外部访问权限/远程连接启用。
- **Claude 显示服务器但没有工具** - 完全退出 Claude Desktop(从托盘),然后重新启动;确认 `npm run login-check` 通过。
## 配置参考
所有可选参数,均通过 `.env` 或环境变量:
| 变量 | 默认值 | 含义 |
|----------|---------|---------|
| `LOXONE_SERIAL` | - | **必填** - Miniserver 序列号(MAC,无冒号) |
| `LOXONE_USER` | - | **必填** - Loxone 用户名 |
| `LOXONE_PASSWORD` | - | **必填** - Loxone 密码 |
| `LOXONE_CONNECT_BASE` | `https://connect.loxonecloud.com` | Loxone Cloud 连接基础 |
| `LOXONE_CACHE_PATH` | `~/.loxone-mcp-proxy/cache.json` | 令牌缓存位置 |
| `LOXONE_REDIRECT_PORT` | `41678` | 仅用作 OAuth 重定向目标的环回端口 |
| `LOXONE_DEBUG` | `0` | 设置 `1` 以进行详细的标准错误日志记录 |
## 开发
```bash
npm test # 单元测试(node:test,无依赖)
LOXONE_INTEGRATION=1 npm run test:integration # 实时测试(需要.env + 设备)
```
布局:
```
src/
index.js 入口点(加载配置,启动_stdio_代理)
proxy.js stdio <-> 连接桥(换行分隔的 JSON-RPC)
connection.js 端点解析,令牌生命周期,会话,恢复
loxoneAuth.js 无头 OAuth(注册,HTML 表单登录,令牌,刷新)
resolver.js connect.loxonecloud.com -> 当前中继,缓存
cache.js 磁盘令牌缓存
config.js .env + 环境配置
logger.js 标准错误日志记录(标准输出保留用于 MCP)
scripts/
login-check.js 独立连接/凭证检查
```
## 许可证
[MIT](./LICENSE) © Ivan Tichy
## 致谢
用于将 Claude 连接到 Loxone 的原生 MCP 服务器。 与 Loxone 无关联或获得其认可。