@owine/unifi-protect-mcp 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,15 +15,7 @@ An MCP (Model Context Protocol) server that exposes UniFi Protect's Integration
15
15
  Add to Claude Code with a single command — no clone or build needed:
16
16
 
17
17
  ```bash
18
- claude mcp add-json unifi-protect '{
19
- "command": "npx",
20
- "args": ["-y", "@owine/unifi-protect-mcp@latest"],
21
- "env": {
22
- "UNIFI_PROTECT_HOST": "192.168.1.1",
23
- "UNIFI_PROTECT_API_KEY": "your-api-key",
24
- "UNIFI_PROTECT_VERIFY_SSL": "false"
25
- }
26
- }' -s user
18
+ claude mcp add-json unifi-protect '{"command":"npx","args":["-y","@owine/unifi-protect-mcp@latest"],"env":{"UNIFI_PROTECT_HOST":"192.168.1.1","UNIFI_PROTECT_API_KEY":"your-api-key","UNIFI_PROTECT_VERIFY_SSL":"false"}}' -s user
27
19
  ```
28
20
 
29
21
  Use `-s user` for global availability across all projects, or `-s project` for the current project only.
@@ -42,15 +34,7 @@ npm run build
42
34
  Then add to Claude Code:
43
35
 
44
36
  ```bash
45
- claude mcp add-json unifi-protect '{
46
- "command": "node",
47
- "args": ["/path/to/unifi-protect-mcp/dist/index.js"],
48
- "env": {
49
- "UNIFI_PROTECT_HOST": "192.168.1.1",
50
- "UNIFI_PROTECT_API_KEY": "your-api-key",
51
- "UNIFI_PROTECT_VERIFY_SSL": "false"
52
- }
53
- }' -s user
37
+ claude mcp add-json unifi-protect '{"command":"node","args":["/path/to/unifi-protect-mcp/dist/index.js"],"env":{"UNIFI_PROTECT_HOST":"192.168.1.1","UNIFI_PROTECT_API_KEY":"your-api-key","UNIFI_PROTECT_VERIFY_SSL":"false"}}' -s user
54
38
  ```
55
39
 
56
40
  ### Environment Variables
@@ -60,7 +44,7 @@ claude mcp add-json unifi-protect '{
60
44
  | `UNIFI_PROTECT_HOST` | Yes | — | IP or hostname of your UniFi Protect console |
61
45
  | `UNIFI_PROTECT_API_KEY` | Yes | — | API key from Protect integration settings |
62
46
  | `UNIFI_PROTECT_VERIFY_SSL` | No | `true` | Set to `false` to skip TLS certificate verification (needed for self-signed certs) |
63
- | `UNIFI_PROTECT_READ_ONLY` | No | `false` | Set to `true` to disable all write/mutating tools (monitoring-only mode) |
47
+ | `UNIFI_PROTECT_READ_ONLY` | No | `true` | Set to `false` to enable write/mutating tools (default is monitoring-only mode) |
64
48
 
65
49
  ### Manual Configuration
66
50
 
@@ -87,7 +71,7 @@ Alternatively, add to your `~/.claude.json` under the top-level `"mcpServers"` k
87
71
  This server provides layered safety controls for responsible operation:
88
72
 
89
73
  - **Tool annotations** — Every tool declares `readOnlyHint` and `destructiveHint` so MCP clients (like Claude Code) can make informed confirmation decisions
90
- - **Read-only mode** — Set `UNIFI_PROTECT_READ_ONLY=true` to completely hide all write/mutating tools. Only read operations (list, get, snapshot) are registered. Ideal for monitoring-only deployments
74
+ - **Read-only mode** — Enabled by default. Only read operations (list, get, snapshot) are registered. Set `UNIFI_PROTECT_READ_ONLY=false` to enable write/mutating tools
91
75
  - **Confirmation parameter** — The most dangerous tools (`protect_disable_mic`, `protect_trigger_alarm_webhook`) require an explicit `confirm: true` parameter that must be present for the call to succeed
92
76
  - **Dry-run support** — All write tools (except those with `confirm`) accept an optional `dryRun: true` parameter that returns a preview of what would happen without making any changes
93
77
 
package/dist/config.js CHANGED
@@ -13,7 +13,7 @@ function loadConfig() {
13
13
  host: process.env.UNIFI_PROTECT_HOST,
14
14
  apiKey: process.env.UNIFI_PROTECT_API_KEY,
15
15
  verifySsl: process.env.UNIFI_PROTECT_VERIFY_SSL !== "false",
16
- readOnly: process.env.UNIFI_PROTECT_READ_ONLY === "true",
16
+ readOnly: process.env.UNIFI_PROTECT_READ_ONLY !== "false",
17
17
  });
18
18
  if (!result.success) {
19
19
  const errors = result.error.issues.map((i) => i.message).join(", ");
@@ -76,7 +76,7 @@ function registerCameraTools(server, client, readOnly) {
76
76
  id: zod_1.z.string().describe("Camera ID"),
77
77
  settings: zod_1.z
78
78
  .record(zod_1.z.string(), zod_1.z.unknown())
79
- .describe("Partial camera settings to update (JSON object)"),
79
+ .describe("Partial camera settings to update. Known fields: name (string), osdSettings (object), ledSettings (object), lcdMessage (object), videoMode (string), hdrType (string), smartDetectSettings (object)"),
80
80
  dryRun: zod_1.z
81
81
  .boolean()
82
82
  .optional()
@@ -162,7 +162,7 @@ function registerCameraTools(server, client, readOnly) {
162
162
  }
163
163
  });
164
164
  server.registerTool("protect_disable_mic", {
165
- description: "IRREVERSIBLE: Permanently disable the microphone on a camera. Cannot be re-enabled.",
165
+ description: "IRREVERSIBLE: Permanently disable the microphone on a camera. Can only be re-enabled by factory resetting the camera.",
166
166
  inputSchema: {
167
167
  id: zod_1.z.string().describe("Camera ID"),
168
168
  confirm: zod_1.z
@@ -5,6 +5,12 @@ const zod_1 = require("zod");
5
5
  const responses_js_1 = require("../utils/responses.js");
6
6
  const READ_ONLY_ANNOTATIONS = { readOnlyHint: true, destructiveHint: false };
7
7
  const WRITE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false };
8
+ const settingsHints = {
9
+ light: "Known fields: name (string), isLightForceEnabled (boolean), lightModeSettings (object with mode, enableAt), lightDeviceSettings (object with isIndicatorEnabled, pirDuration, pirSensitivity, ledLevel)",
10
+ sensor: "Known fields: name (string), mountType (string), motionSettings (object), humiditySettings (object), temperatureSettings (object), lightSettings (object), alarmSettings (object)",
11
+ chime: "Known fields: name (string), volume (number), ringSettings (array of ring tone configurations)",
12
+ viewer: "Known fields: name (string), liveview (string, liveview ID to display)",
13
+ };
8
14
  function registerDeviceCrud(server, client, deviceType, readOnly) {
9
15
  const plural = `${deviceType}s`;
10
16
  const label = deviceType.charAt(0).toUpperCase() + deviceType.slice(1);
@@ -40,7 +46,7 @@ function registerDeviceCrud(server, client, deviceType, readOnly) {
40
46
  id: zod_1.z.string().describe(`${label} ID`),
41
47
  settings: zod_1.z
42
48
  .record(zod_1.z.string(), zod_1.z.unknown())
43
- .describe(`Partial ${deviceType} settings to update (JSON object)`),
49
+ .describe(`Partial ${deviceType} settings to update. ${settingsHints[deviceType]}`),
44
50
  dryRun: zod_1.z
45
51
  .boolean()
46
52
  .optional()
@@ -37,8 +37,8 @@ function registerFileTools(server, client, readOnly) {
37
37
  annotations: DESTRUCTIVE_ANNOTATIONS,
38
38
  }, async ({ id }) => {
39
39
  try {
40
- const data = await client.post(`/alarm-manager/webhook/${id}`);
41
- return (0, responses_js_1.formatSuccess)(data);
40
+ await client.post(`/alarm-manager/webhook/${id}`);
41
+ return (0, responses_js_1.formatSuccess)({ triggered: true, webhookId: id });
42
42
  }
43
43
  catch (err) {
44
44
  return (0, responses_js_1.formatError)(err);
@@ -38,7 +38,7 @@ function registerLiveviewTools(server, client, readOnly) {
38
38
  inputSchema: {
39
39
  settings: zod_1.z
40
40
  .record(zod_1.z.string(), zod_1.z.unknown())
41
- .describe("Liveview configuration (JSON object with name, slots, etc.)"),
41
+ .describe("Liveview configuration. Required fields: name (string), layout (number of slots), slots (array of {cameras: string[], cycleMode: string, cycleInterval: number}), isDefault (boolean), isGlobal (boolean)"),
42
42
  dryRun: zod_1.z
43
43
  .boolean()
44
44
  .optional()
@@ -63,7 +63,7 @@ function registerLiveviewTools(server, client, readOnly) {
63
63
  id: zod_1.z.string().describe("Liveview ID"),
64
64
  settings: zod_1.z
65
65
  .record(zod_1.z.string(), zod_1.z.unknown())
66
- .describe("Partial liveview settings to update (JSON object)"),
66
+ .describe("Partial liveview settings to update. Known fields: name (string), layout (number), slots (array of {cameras: string[], cycleMode: string, cycleInterval: number}), isDefault (boolean), isGlobal (boolean)"),
67
67
  dryRun: zod_1.z
68
68
  .boolean()
69
69
  .optional()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owine/unifi-protect-mcp",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "MCP server for UniFi Protect Integration API",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -42,6 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@eslint/js": "9.39.2",
44
44
  "@types/node": "24.10.13",
45
+ "@vitest/coverage-v8": "4.0.18",
45
46
  "eslint": "9.39.2",
46
47
  "typescript": "5.9.3",
47
48
  "typescript-eslint": "8.55.0",