Files
qianbian/docs/sdk/MCP.md

158 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ppclock-mcp · MCP 服务器参考(v0.2.0-beta)
> 面向 agent 挂载与部署的工具级参考。SDK 签名见 `docs/sdk/API.md`,协议细节见 `docs/protocol.md`。
## 一句话
ppclock-mcp 是跑在**蓝牙物理所在机器**上的单层 MCP 服务器:13 个工具覆盖对时 / 模式 /
切换 / 传图 / 模板 / 倒计时 / 日历文字 / 休眠 / 刷屏 / raw 逃生舱,agent 经 stdio 或
streamable HTTP 直接驱动 4.2" 墨水屏设备。
## 安装
```bash
pip install -e ".[mcp]" # 追加依赖:mcp>=1.23,<2
```
要求:Python ≥ 3.10、本机蓝牙适配器(bleak)。服务器与被控设备在同一台蓝牙主机上;
不需要互联网。
## 启动
入口为 console script `ppclock-mcp`(`python -m ppclock.mcp_server` 不受支持)。
```bash
# stdio —— 本机 agent 直挂
ppclock-mcp --mac AA:BB:CC:DD:EE:FF
# streamable HTTP —— 局域网 agent 平台远程调用
PPCLOCK_MCP_TOKEN=<token> ppclock-mcp --transport http --host 0.0.0.0 --port 8972
```
启动参数:
| 参数 | 默认 | 说明 |
|---|---|---|
| `--transport` | `stdio` | `stdio` / `http` |
| `--host` | `127.0.0.1` | HTTP 绑定地址;绑非回环地址**必须**设 `PPCLOCK_MCP_TOKEN`,否则拒绝启动 |
| `--port` | `8972` | HTTP 端口 |
| `--mac` | 无 | 设备 MAC;缺省自动扫描 `NRF-` 前缀并记住实际地址 |
| `--connect-timeout` | `90.0` | 守候连接秒数(覆盖设备 30-60s 广播窗口) |
| `--idle-timeout` | `300.0` | 空闲断开秒数;`0` = 每次调用独立连接 |
| `--allowed-hosts` | 无 | HTTP Host 白名单(逗号分隔)。HTTP 模式绑非回环地址时**必须**把本机的局域网地址加进来(如 `192.168.61.35:8972,localhost:8972`),否则 LAN Host 请求被 DNS 重绑定防护拒绝(421);缺省仅 localhost 族 |
环境变量:`PPCLOCK_MCP_TOKEN` —— HTTP 模式 Bearer 鉴权令牌(仅 HTTP 使用;stdio 无鉴权,
信任本机进程)。
## Agent 挂载示例
```bash
# Claude Code(stdio)
claude mcp add ppclock -- ppclock-mcp --mac AA:BB:CC:DD:EE:FF
```
streamable HTTP 端点为 `http://<host>:8972/mcp`,请求头携带
`Authorization: Bearer <PPCLOCK_MCP_TOKEN>`;token 不符返回 401。HTTP 为无状态模式
(`stateless_http`),无需会话保持。
## 13 工具参考
全部工具 async,返回统一契约(见下节)。参数与行为逐字对齐 `src/ppclock/mcp_tools.py`。
| 工具 | 参数 | 说明 |
|---|---|---|
| `scan_devices` | `timeout: float = 5.0` | 扫描附近 NRF- 前缀墨水屏设备;返回形状 `data.devices = [{name,address,rssi}]` |
| `device_status` | 无 | 查询连接状态/绑定 MAC/设备 ID/空闲秒数。会触发连接守候(最长 `--connect-timeout` 秒)并刷新空闲计时——周期轮询本工具会阻止 idle 自动断开 |
| `set_time` | `tz: float = 8.0` | 对时;tz 为时区偏移(默认 8=北京时间) |
| `set_mode` | `mode: str` | 切换显示模式:`clock1-3`/`calendar1-3`/`image0-3`/`tricolor`/`mono` |
| `toggle` | `name: str` | 单字节切换:`invert`/`font`/`rotate180`/`hour_format`/`clock_color` |
| `upload_image` | `source: str, slot: int = 0, algo: str = "atkinson", mono: bool = False, threshold/diffusion/brightness/contrast/saturation/rotate: float\|None = None` | 传图到指定槽位。`source` = 服务器路径或 base64(可带 data URI 前缀);`algo` ∈ `none/floydsteinberg/atkinson/bayer/stucki/jarvis`;6 个可选 adjust 参数:`threshold`/`diffusion`/`brightness`/`contrast`/`saturation`/`rotate`(仅显式传入时生效) |
| `render_template` | `name: str, payload: dict\|None = None, slot: int = 0, algo: str = "atkinson"` | 本地渲染模板并上传:`schedule`/`businesscard`/`memo`/`course`/`qrcode`/`custom` |
| `countdown` | `date: str, mode: str = "clock", prefix: str\|None = None` | 倒计时;`date` 为 ISO `YYYY-MM-DD`;`mode` = `clock`/`calendar` |
| `countdown_off` | 无 | 关闭倒计时 |
| `calendar_text` | `text: str` | 设置日历中文文字并切到日历模式一 |
| `set_sleep` | `on: bool, start_h: int, end_h: int` | 设置休眠时段(0-23 时) |
| `clear_screen` | 无 | 刷屏(EPD 清屏) |
| `raw_send` | `data_hex: str, channel: str = "alt"` | 逃生舱:直发十六进制字节串;`channel` = `rxtx`/`epd`/`alt` |
`device_status` 返回字段:`connected` / `mac` / `device_id`(尽力而为,失败省略)/
`idle_seconds` / `connect_count` / `connect_timeout` / `idle_timeout`。
## 输出契约与错误码
与 CLI `--json` 同构的统一契约:
```json
成功 {"ok": true, "tool": "<name>", "data": {...}}
失败 {"ok": false, "tool": "<name>", "error": {"code", "message", "hint"?}}
```
错误码(5 种):
| code | 触发 | hint |
|---|---|---|
| `CONNECT_TIMEOUT` | 守候窗口内未连上设备 | 设备长睡眠、广播窗口 30-60s;调大 `--connect-timeout` 或稍后重试 |
| `DEVICE_NOT_FOUND` | 扫描未发现设备 | 未发现 NRF- 前缀设备;确认设备在位且未处于深睡 |
| `BLE_ERROR` | 其他传输层错误 | 无 hint |
| `INVALID_PARAM` | 参数校验失败(非法 base64/非图片/非法日期/非法 hex 等) | 无 hint,message 给出具体原因 |
| `INTERNAL` | 工具层兜底未分类异常 | 无 hint,message 为 `类型: 详情` |
`hint` 仅对 `CONNECT_TIMEOUT` / `DEVICE_NOT_FOUND` 出现,语义是给 agent 的可执行恢复建议
(调参重试 / 检查设备),不是给人看的错误描述。
## 连接行为(方案 C:按需连接 + 空闲保持)
- **守候窗口**:首次操作发起连接,守候 `--connect-timeout` 秒(默认 90s,覆盖设备
30-60s 广播窗口);设备在深睡中也能等到其广播。
- **空闲超时**:操作完成后保持连接;空闲 `--idle-timeout` 秒(默认 300s)自动断开让
设备回睡眠。`--idle-timeout 0` 退化为每次调用独立连接。
- **串行**:所有设备操作经一把锁串行执行(BLE 适配器独占),并发调用自动排队,不会
互相打断。
- **重连一次**:操作中连接丢失(传输层异常全家:SDK `TransportError`、bleak 裸抛
`BleakError`、平台 `OSError`/超时)→ 断开重连并重试一次,再失败才向上返回错误。
## Windows 部署(蓝牙所在机)
```powershell
python -m venv .venv
.venv\Scripts\pip install -e ".[mcp]"
setx PPCLOCK_MCP_TOKEN "<token>" # 或按服务方式注入环境变量
.venv\Scripts\ppclock-mcp --transport http --host 0.0.0.0 --port 8972 `
--allowed-hosts "192.168.61.35:8972,localhost:8972" # 换成本机局域网地址
```
- 防火墙放行 **8972/tcp**,仅对局域网段开放(勿暴露公网;token 是唯一防线)。
- `--allowed-hosts` 必须包含本机的局域网地址(Host 头按 `<IP>:<port>` 匹配),
漏配时局域网请求被 mcp 的 DNS 重绑定防护拒绝(421)。
- 不动既有 **8971** bridge 与其他服务:8972 为独立端口、独立进程。
- `--mac` 建议显式指定设备地址,避免自动扫描误绑其他 `NRF-` 设备。
### 常驻部署(2026-07-31 生产性测试实例)
参考实现(banyWinServer 192.168.61.35,v0.2.0-beta,熠管家运维):
- **常驻方式**:SYSTEM 计划任务 `BanyTech-PPCLOCK-MCP`(AtStartup、最高权限、
执行时间不限、Task Scheduler 999 次/1 分钟恢复);runner 脚本另有 5 秒子进程重启。
- **token**:Windows CSPRNG 生成,存 `C:\ProgramData\OpenClaw\ppclock-mcp\token.txt`,
ACL 仅 SYSTEM/Administrators 可读;不入仓、不写日志。
- **日志**:同目录 `logs\`——`supervisor.log` 记版本/启动参数/退出,
stdout/stderr 按次落文件。
- **验证基线**:真实 LAN Host initialize 200、`device_status` ok;
杀 8972 进程后 5 秒自动拉起、initialize 复 200。
- agent 挂载:`http://192.168.61.35:8972/mcp` + `Authorization: Bearer <token>`。
- 运维记录原件:`/home/cwmine/vps/host-win/maintain_logs/2026-07-31_ppclock-mcp-production-test-service.md`。
## 不暴露的能力
以下 SDK 能力**有意不**注册为 MCP 工具:
| 能力 | 原因 |
|---|---|
| OTA 固件升级(`ota`) | 高危写闪存,agent 误调用可砖机;只走 CLI 显式 `--yes` 授权路径 |
| 激活(`activate`/`activate_auto`) | 一次性出厂级操作,日常 agent 场景不需要;保留在 SDK/CLI |
| LUT 校准(`lut`) | 面板厂商级调试参数,误刷影响显示质量 |
| WiFi 配置(`wifi`) | 本固件为 no-op(固件证据),暴露只会误导 agent |
| 轮播(`rotation`) | 本固件为 no-op(固件证据),同上 |
需要这些能力时按 `docs/sdk/API.md` 走 SDK/CLI,不经 MCP 面。