# 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= 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://:8972/mcp`,请求头携带 `Authorization: Bearer `;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": "", "data": {...}} 失败 {"ok": false, "tool": "", "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 "" # 或按服务方式注入环境变量 .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 头按 `:` 匹配), 漏配时局域网请求被 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 `。 - 运维记录原件:`/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 面。