SuperAPI AI 工具集

unifi-protect-tools

智能体与模型

查看项目

更新于 11-02 01:23

技能介绍

用途

unifi-protect-tools 是 unifi-mcp-server 项目中关于 UniFi Protect 工具开发的内部说明文档。它面向需要新增、修改或测试 Protect 工具(任何 src/tools/protect_*.py 模块)的开发者,要求在动手前先阅读。Protect 工具通过本地控制台上的 Protect 集成 API 进行通信。通用的“添加工具”流程另见 unifi-mcp-tool-builder。

模块结构

文档列出 15 个模块、71 个函数和 71 个已注册工具名,例如:

  • src/tools/protect_alarm.py(8 个工具,如 list_protect_arm_profiles、set_current_protect_arm_profile、disable_protect_alarm)
  • src/tools/protect_alarm_hubs.py(4 个)
  • src/tools/protect_bridges.py(3 个)
  • src/tools/protect_cameras.py(2 个,如 list_protect_cameras、get_protect_camera)
  • src/tools/protect_devices.py(13 个)
  • src/tools/protect_events.py(6 个)
  • src/tools/protect_fobs.py(3 个)
  • src/tools/protect_link_stations.py(3 个)
  • src/tools/protect_nvr.py(2 个)
  • src/tools/protect_pos.py(1 个)
  • src/tools/protect_relays.py(4 个)
  • src/tools/protect_sirens.py(6 个)
  • src/tools/protect_speakers.py(4 个)
  • src/tools/protect_users.py(4 个)
  • src/tools/protect_views.py(8 个)

Protect 的 MCP 资源位于 src/resources/protect.py;原始的 connector_protect_* 代理工具位于 src/tools/connector.py,属于云端工具集。

注册规则

模块中每个公开的 async def 都会被注册为工具(由 src/tool_registry.py 的 register_module_tools() 完成),辅助函数需以下划线开头。Protect 模块只出现在 src/main.py 的 _LOCAL_TOOL_MODULES 中,云端模式不存在。新增模块需同时加入 src/main.py 的 _LOCAL_TOOL_MODULES、protect profile 和 system profile。Protect 读取不做缓存,相关模块不在 src/cache.py 的 CACHED_MODULES 中。

HTTP 调用方式

需使用 src/api/protect_client.py 中的 ProtectClient,而非 UniFiClient。所有路径都用 settings.get_protect_integration_path("<endpoint>") 构造,本地模式下得到 /proxy/protect/integration/v1/<endpoint>。首次调用需 await client.authenticate(),它通过 GET cameras 检查访问权限。请求体用 json_data= 而非 json= 传递;状态码 400 及以上会抛出 APIError。文档给出了使用 async with ProtectClient(settings) as client 的示例代码。

数据模型

v7 模型位于 src/models/protect_v7.py,继承 _ProtectV7Base,设置 populate_by_name=True 和 extra="allow",以保证新固件的未知字段不会导致解析失败。字段使用 snake_case,并带 camelCase 的 alias;导出时用 model_dump(by_alias=True)。旧模型(如 src/models/protect_camera.py、src/models/protect_nvr.py)不允许额外字段,新工作不要照搬。新模型需从 src/models/init.py 导出。

写入型工具的实现顺序

以 src/tools/protect_relays.py 的 update_protect_relay 为参考,顺序固定:先调用 validate_confirmation(confirm, "<operation>", dry_run);再校验 ID 与参数,输入有误抛出 ValidationError;然后由非 None 参数构建 payload,为空则抛 ValidationError;若 coerce_bool(dry_run) 为真,返回 {"dry_run": True, "operation": "<tool name>", ..., "payload": payload};只有以上通过后才打开 ProtectClient。不需要自己写审计代码,注册表包装器会审计所有变更调用。名称中含 disable、reset 等风险词的工具必须列入 DESTRUCTIVE_TOOLS(src/tool_registry.py)或 RISKY_NAME_EXEMPTIONS(tests/unit/fleet/test_tiers.py)。

响应结构

列表返回 {"offset", "limit", "count", "totalCount", "data": [...]},使用 validate_limit_offset()(默认 100,范围 1 到 1000)。获取或更新返回 Model.model_validate(_extract_item(response)).model_dump(by_alias=True)。动作返回 {"success": True, <ids>, "response": ...}。_extract_collection 和 _extract_item 可接受裸值或 {"data": ...} 包装,每个模块各有一份副本。日志需经 sanitize_log_message() 处理。

测试

测试文件位于 tests/unit/tools/test_protect_<module>.py,代表文件为 tests/unit/tools/test_protect_relays.py。通过 patch("src.tools.protect_relays.ProtectClient", return_value=mock_client) 替换客户端,mock_client 是 AsyncMock,aenter 返回自身,aexit 返回 False。需断言确切的路径与请求体:mock_client.patch.assert_awaited_once_with(path, json_data=...)。每个写入工具都要测试未确认(以及 confirm="false")时被拒绝、拒绝时不创建客户端、以及 dry run 不发请求。tests/unit/tools/test_protect_write_gates.py 会检查所有 Protect 写入的 confirm 与 dry_run。新增或重命名工具后,运行 UPDATE_TOOL_TIERS=1 pytest tests/unit/fleet/test_tiers.py。

注意事项

规范基线为 Protect v7.3.68(scripts/protect-api-spec-v7.3.68.json),调用端点前应先在该文件中核对。Phase 5a 的写入路径仅针对 mock 测试过,尚未在真实硬件上验证,修改时应予以说明。get_protect_arm_profile 调用的 GET 路径不在 v7.3.68 规范中,可能返回 404。docs/UNIFI_API.md 开头仍称 Protect v7 为“planned”,与其后面的 Phase 5a 章节矛盾。已知缺陷:list_protect_device_updates 只在 src/tools/protect_devices.py 定义一次,src/tools/protect_events.py 为其两个兼容包装器导入该函数,register_module_tools 会跳过导入的函数,因此只注册一次;若某工具名在两个模块中定义,tests/unit/test_main_runtime.py 的 TestToolModuleSets 会失败。