@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 +4 -20
- package/dist/config.js +1 -1
- package/dist/tools/cameras.js +2 -2
- package/dist/tools/devices.js +7 -1
- package/dist/tools/files.js +2 -2
- package/dist/tools/liveviews.js +2 -2
- package/package.json +2 -1
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 | `
|
|
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** —
|
|
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
|
|
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(", ");
|
package/dist/tools/cameras.js
CHANGED
|
@@ -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 (
|
|
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.
|
|
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
|
package/dist/tools/devices.js
CHANGED
|
@@ -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
|
|
49
|
+
.describe(`Partial ${deviceType} settings to update. ${settingsHints[deviceType]}`),
|
|
44
50
|
dryRun: zod_1.z
|
|
45
51
|
.boolean()
|
|
46
52
|
.optional()
|
package/dist/tools/files.js
CHANGED
|
@@ -37,8 +37,8 @@ function registerFileTools(server, client, readOnly) {
|
|
|
37
37
|
annotations: DESTRUCTIVE_ANNOTATIONS,
|
|
38
38
|
}, async ({ id }) => {
|
|
39
39
|
try {
|
|
40
|
-
|
|
41
|
-
return (0, responses_js_1.formatSuccess)(
|
|
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);
|
package/dist/tools/liveviews.js
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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": "
|
|
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",
|