@owine/unifi-protect-mcp 2.15.0 → 3.1.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 +249 -99
- package/dist/client.d.ts +36 -2
- package/dist/client.js +242 -83
- package/dist/config.d.ts +7 -1
- package/dist/config.js +114 -6
- package/dist/index.js +14 -6
- package/dist/instructions.d.ts +6 -0
- package/dist/instructions.js +24 -0
- package/dist/schemas/cameras.d.ts +32 -30
- package/dist/schemas/cameras.js +5 -5
- package/dist/schemas/common.d.ts +31 -6
- package/dist/schemas/common.js +45 -6
- package/dist/schemas/devices.d.ts +2 -0
- package/dist/schemas/devices.js +1 -1
- package/dist/schemas/misc.d.ts +12 -1
- package/dist/schemas/misc.js +3 -2
- package/dist/tls.d.ts +41 -0
- package/dist/tls.js +75 -0
- package/dist/tools/alarm-manager.d.ts +2 -2
- package/dist/tools/alarm-manager.js +13 -23
- package/dist/tools/arm-profiles.d.ts +8 -2
- package/dist/tools/arm-profiles.js +81 -86
- package/dist/tools/cameras.d.ts +2 -2
- package/dist/tools/cameras.js +115 -186
- package/dist/tools/device-actions.d.ts +2 -2
- package/dist/tools/device-actions.js +84 -97
- package/dist/tools/devices.d.ts +5 -3
- package/dist/tools/devices.js +30 -35
- package/dist/tools/files.d.ts +2 -2
- package/dist/tools/files.js +47 -30
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +14 -11
- package/dist/tools/liveviews.d.ts +2 -2
- package/dist/tools/liveviews.js +29 -47
- package/dist/tools/pos.d.ts +3 -3
- package/dist/tools/pos.js +11 -18
- package/dist/tools/registrar.d.ts +34 -0
- package/dist/tools/registrar.js +103 -0
- package/dist/tools/subscriptions.d.ts +13 -2
- package/dist/tools/subscriptions.js +148 -27
- package/dist/tools/system.d.ts +2 -2
- package/dist/tools/system.js +4 -14
- package/dist/tools/users.d.ts +2 -2
- package/dist/tools/users.js +18 -35
- package/dist/utils/limits.d.ts +33 -0
- package/dist/utils/limits.js +74 -0
- package/dist/utils/paging.d.ts +15 -0
- package/dist/utils/paging.js +28 -0
- package/dist/utils/params.d.ts +13 -0
- package/dist/utils/params.js +40 -0
- package/dist/utils/responses.js +2 -3
- package/dist/utils/safety.d.ts +39 -3
- package/dist/utils/safety.js +45 -3
- package/dist/utils/url.d.ts +4 -1
- package/dist/utils/url.js +4 -1
- package/dist/version.d.ts +8 -0
- package/dist/version.js +13 -0
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# UniFi Protect MCP Server
|
|
2
2
|
|
|
3
|
-
An MCP (Model Context Protocol) server that exposes UniFi Protect's Integration REST API as tools for Claude Code and other MCP clients. Aligned with UniFi Protect API **7.3.70
|
|
3
|
+
An MCP (Model Context Protocol) server that exposes UniFi Protect's Integration REST API as tools for Claude Code and other MCP clients. Aligned with UniFi Protect API **7.3.70**, with tools covering cameras, lights, sensors, chimes, viewers, sirens, fobs, relays, speakers, bridges, link stations, alarm hubs, arm profiles, live views, files, users, point-of-sale events, NVR status, and WebSocket subscriptions.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
7
|
-
- Node.js
|
|
7
|
+
- A Node.js release that `engines.node` in `package.json` accepts (each supported LTS line is tested in CI)
|
|
8
8
|
- A UniFi Protect system with the Integration API enabled
|
|
9
|
-
- An API key generated from your UniFi Protect console
|
|
9
|
+
- An API key generated from your UniFi Protect console (see [Choosing an API key](#choosing-an-api-key))
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
12
12
|
|
|
@@ -15,11 +15,15 @@ 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 '{"command":"npx","args":["-y","@owine/unifi-protect-mcp@latest"],"env":{"UNIFI_PROTECT_HOST":"192.168.1.1","UNIFI_PROTECT_API_KEY":"your-api-key"
|
|
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"}}' -s user
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Use `-s user` for global availability across all projects, or `-s project` for the current project only.
|
|
22
22
|
|
|
23
|
+
A local console usually has a self-signed certificate, so the first call fails with a certificate error until you trust it: see [Self-signed console certificates](#self-signed-console-certificates).
|
|
24
|
+
|
|
25
|
+
`@latest` runs the newest release every time the server starts, including a new major version. To take fixes but choose when to make breaking upgrades, pin the major version you have tested instead (`@owine/unifi-protect-mcp@<major>`).
|
|
26
|
+
|
|
23
27
|
### From source
|
|
24
28
|
|
|
25
29
|
If you prefer to build locally:
|
|
@@ -37,22 +41,42 @@ This project uses pnpm — `npm install` ignores `pnpm-lock.yaml` and may resolv
|
|
|
37
41
|
Then add to Claude Code:
|
|
38
42
|
|
|
39
43
|
```bash
|
|
40
|
-
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"
|
|
44
|
+
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"}}' -s user
|
|
41
45
|
```
|
|
42
46
|
|
|
43
47
|
### Environment Variables
|
|
44
48
|
|
|
45
49
|
| Variable | Required | Default | Description |
|
|
46
50
|
|---|---|---|---|
|
|
47
|
-
| `UNIFI_PROTECT_HOST` | Yes | — | IP or hostname of your UniFi Protect console |
|
|
48
|
-
| `UNIFI_PROTECT_API_KEY` | Yes | — | API key from
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
+
| `UNIFI_PROTECT_HOST` | Yes | — | IP or hostname of your UniFi Protect console, optionally with `:port`. A leading `https://` and trailing `/` are stripped; bracket IPv6 addresses |
|
|
52
|
+
| `UNIFI_PROTECT_API_KEY` | Yes | — | API key from the console's integration settings (see [Choosing an API key](#choosing-an-api-key)). Surrounding whitespace is trimmed; a key with a control character inside is refused at startup |
|
|
53
|
+
| `UNIFI_PROTECT_CA_CERT` | No | — | Absolute path to a PEM file with the console's certificate, to verify a self-signed console without turning verification off (see below). Not allowed with `api.ui.com` or together with `UNIFI_PROTECT_VERIFY_SSL=false` |
|
|
54
|
+
| `UNIFI_PROTECT_VERIFY_SSL` | No | `true` | Set to `false` (or `0`/`no`/`off`, any case) to skip certificate verification for this server's connections to the console. Prefer `UNIFI_PROTECT_CA_CERT`. Not allowed with `api.ui.com`. Unrecognized values keep the default |
|
|
55
|
+
| `UNIFI_PROTECT_READ_ONLY` | No | `true` | Set to `false` (or `0`/`no`/`off`, any case) to enable write tools (default is monitoring-only mode). Unrecognized values keep the default |
|
|
51
56
|
| `UNIFI_PROTECT_CONSOLE_ID` | No | — | Cloud Connector console ID. Only used when `UNIFI_PROTECT_HOST` is `api.ui.com`; ignored (with a warning on stderr) otherwise |
|
|
52
57
|
|
|
58
|
+
The startup line in the MCP host's server log says whether the server is read-only or read-write. A configuration error names the variable at fault, and the server exits.
|
|
59
|
+
|
|
60
|
+
### Self-signed console certificates
|
|
61
|
+
|
|
62
|
+
UniFi consoles ship with a self-signed certificate, so the first call to a local console usually fails with a certificate error. The safe fix is to trust that one certificate. Save it:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
openssl s_client -connect 192.168.1.1:443 </dev/null 2>/dev/null | openssl x509 > ~/.config/unifi-protect-console.pem
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Set `UNIFI_PROTECT_CA_CERT` to the file's **absolute** path (for example `/home/you/.config/unifi-protect-console.pem`); MCP hosts do not expand `~` or `$HOME` in `env`. The server then accepts exactly that certificate, even though it is issued to a name like `unifi.local` and you connect by IP. This also works for a certificate issued by a CA (one you installed on the console yourself): pinning the console's own certificate is enough, without the CA's. Any other certificate must chain to the file **and** match the hostname. If the network between you and the console is not trusted, compare the fingerprint (`openssl x509 -in <file> -noout -fingerprint -sha256`) with the console's. If the console later generates a new certificate, save it again.
|
|
69
|
+
|
|
70
|
+
Setting `UNIFI_PROTECT_VERIFY_SSL=false` instead turns certificate checks off for this server's connections to the console, and only those (other TLS connections in the process are unaffected). Anyone who can intercept that traffic can then read the API key, so use it only on a network you trust.
|
|
71
|
+
|
|
72
|
+
### Choosing an API key
|
|
73
|
+
|
|
74
|
+
- **Use the least-privileged key that works.** Create the key from an admin account with no more access than you need. For the default read-only mode, that means an admin with view-only access to Protect; use a full-access admin only with `UNIFI_PROTECT_READ_ONLY=false`.
|
|
75
|
+
- **Prefer a local console key to a Site Manager key.** A Site Manager key (Cloud Connector, `api.ui.com`) reaches every console on the account and travels over the internet.
|
|
76
|
+
|
|
53
77
|
### Manual Configuration
|
|
54
78
|
|
|
55
|
-
Alternatively, add to your `~/.claude.json` under the top-level `"mcpServers"` key:
|
|
79
|
+
Alternatively, add to your `~/.claude.json` under the top-level `"mcpServers"` key, or to Claude Desktop's `claude_desktop_config.json` the same way:
|
|
56
80
|
|
|
57
81
|
```json
|
|
58
82
|
{
|
|
@@ -63,18 +87,13 @@ Alternatively, add to your `~/.claude.json` under the top-level `"mcpServers"` k
|
|
|
63
87
|
"env": {
|
|
64
88
|
"UNIFI_PROTECT_HOST": "192.168.1.1",
|
|
65
89
|
"UNIFI_PROTECT_API_KEY": "your-api-key",
|
|
66
|
-
"
|
|
90
|
+
"UNIFI_PROTECT_CA_CERT": "/absolute/path/to/console.pem"
|
|
67
91
|
}
|
|
68
92
|
}
|
|
69
93
|
}
|
|
70
94
|
}
|
|
71
95
|
```
|
|
72
96
|
|
|
73
|
-
## Safety Features
|
|
74
|
-
|
|
75
|
-
This server provides layered safety controls for responsible operation:
|
|
76
|
-
|
|
77
|
-
- **Tool annotations** — Every tool declares `readOnlyHint` and `destructiveHint` so MCP clients (like Claude Code) can make informed confirmation decisions
|
|
78
97
|
### Cloud Connector
|
|
79
98
|
|
|
80
99
|
To reach a console through Ubiquiti's Cloud Connector instead of over the local
|
|
@@ -82,7 +101,12 @@ network, set `UNIFI_PROTECT_HOST=api.ui.com` and `UNIFI_PROTECT_CONSOLE_ID` to
|
|
|
82
101
|
the console's ID, and supply a **Site Manager** API key (unifi.ui.com →
|
|
83
102
|
Settings → API) as `UNIFI_PROTECT_API_KEY`. That is a different credential
|
|
84
103
|
from a local console API key. Requests are routed through
|
|
85
|
-
`/v1/connector/consoles/{id}`.
|
|
104
|
+
`/v1/connector/consoles/{id}`.
|
|
105
|
+
|
|
106
|
+
TLS verification stays on: the server refuses to start with
|
|
107
|
+
`UNIFI_PROTECT_VERIFY_SSL=false` or `UNIFI_PROTECT_CA_CERT` for `api.ui.com`.
|
|
108
|
+
Ubiquiti's cloud has a publicly trusted certificate, and a pinned certificate
|
|
109
|
+
would replace the public certificate authorities for this server's connections.
|
|
86
110
|
|
|
87
111
|
Find your console ID with:
|
|
88
112
|
|
|
@@ -103,162 +127,292 @@ running Protect will answer.
|
|
|
103
127
|
Setting `UNIFI_PROTECT_CONSOLE_ID` against a local host is not fatal: the
|
|
104
128
|
server logs a warning and talks to that host directly.
|
|
105
129
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
130
|
+
## Upgrading to 3.0
|
|
131
|
+
|
|
132
|
+
3.0 puts more actions behind confirmation: physical outputs, disarming, anything that weakens alarm coverage, and live video and two-way audio. It also scopes TLS settings to this server, keeps every result within what one MCP message can carry, and makes cancellation stop the request to Protect.
|
|
133
|
+
|
|
134
|
+
**If you use the server only through an LLM client** (Claude Code, Claude Desktop, …), the model reads the new parameters from the tool schemas, so most setups need no change. Check three things:
|
|
135
|
+
|
|
136
|
+
- **Cloud Connector with verification off no longer starts.** Unset `UNIFI_PROTECT_VERIFY_SSL` when `UNIFI_PROTECT_HOST=api.ui.com`.
|
|
137
|
+
- **Spellings of `UNIFI_PROTECT_READ_ONLY` / `UNIFI_PROTECT_VERIFY_SSL`** that 2.x ignored now take effect (table below).
|
|
138
|
+
- **Expect more approval prompts.** Update tools and every confirm-gated tool are now annotated destructive (below).
|
|
139
|
+
|
|
140
|
+
Read on if you call tools from scripts or saved agent configurations, or keep allow-lists keyed on tool annotations.
|
|
141
|
+
|
|
142
|
+
### Check your environment variables first
|
|
143
|
+
|
|
144
|
+
2.x treated only the exact string `false` as false. 3.0 also accepts `0`, `no` and `off`, in any case, so a value that 2.x silently ignored now takes effect:
|
|
145
|
+
|
|
146
|
+
| Setting | 2.x | 3.0 |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `UNIFI_PROTECT_READ_ONLY=0` (or `no`, `off`, `FALSE`) | Read-only | ⚠️ **Write tools enabled** |
|
|
149
|
+
| `UNIFI_PROTECT_VERIFY_SSL=0` (or `no`, `off`, `FALSE`) | Certificate verified | Verification **skipped** |
|
|
150
|
+
| `UNIFI_PROTECT_HOST=api.ui.com` with `UNIFI_PROTECT_VERIFY_SSL=false` (or `0`/`no`/`off`) | Verification skipped | ⚠️ **Startup fails**: "TLS verification cannot be disabled for api.ui.com". Unset `UNIFI_PROTECT_VERIFY_SSL` |
|
|
151
|
+
| `UNIFI_PROTECT_VERIFY_SSL=false` | Turned verification off for **every** TLS connection in the process | Applies only to this server's connections to the console. Prefer the new `UNIFI_PROTECT_CA_CERT` |
|
|
152
|
+
| `UNIFI_PROTECT_API_KEY` pasted with a trailing newline or spaces | Sent as is; every call failed and the error quoted the key | Trimmed |
|
|
153
|
+
| `UNIFI_PROTECT_API_KEY` with a control character, or any character that is not printable ASCII (a zero-width space, a smart quote), inside | Same | Startup fails with a message that does not repeat the key |
|
|
154
|
+
|
|
155
|
+
Any other unrecognized value keeps the safe default (read-only, verification on), as before. New: `UNIFI_PROTECT_CA_CERT` verifies a console's self-signed certificate (see [Self-signed console certificates](#self-signed-console-certificates)). Configuration errors now name the variable at fault, and the startup log line says whether the server is read-only or read-write.
|
|
156
|
+
|
|
157
|
+
### Tool input changes (breaking)
|
|
158
|
+
|
|
159
|
+
| Tool | 2.x | 3.0 |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `protect_disable_arm_alarm` | No confirmation | Requires `confirm: true` |
|
|
162
|
+
| `protect_activate_relay_output` | Omitting `state` toggled the output | Requires `confirm: true` **and** `state` (`"on"` / `"off"`) |
|
|
163
|
+
| `protect_trigger_alarm_hub_output` | Omitting `enable` toggled the output | Requires `confirm: true` **and** `enable` (`true` / `false`) |
|
|
164
|
+
| `protect_create_rtsp_stream`, `protect_create_talkback` | No confirmation | Requires `confirm: true`; the returned URLs are credentials |
|
|
165
|
+
| `protect_stop_siren` | No confirmation | Requires `confirm: true` |
|
|
166
|
+
| `protect_delete_arm_profile`, `protect_set_current_arm_profile` | No confirmation | Requires `confirm: true` |
|
|
167
|
+
| `protect_update_sensor` | No confirmation | Requires `confirm: true` when `settings` include `alarmSettings`, `motionSettings`, `glassBreakSettings`, `armProfileIds` or `scheduleMode` |
|
|
168
|
+
| `protect_update_fob` | No confirmation | Requires `confirm: true` when `settings` include `armControlSettings` |
|
|
169
|
+
| `protect_update_arm_profile` | No confirmation | Requires `confirm: true` when `settings` include `schedules`, `automations`, `activationDelay` or `recordEverything` |
|
|
170
|
+
| `protect_update_siren` | No confirmation | Requires `confirm: true` when `settings` include `volume` |
|
|
171
|
+
| Every tool that takes an ID | Any string | Only IDs as list and get tools return them: letters, digits, `-` and `_`. An arm profile ID cannot be `settings`, `enable` or `disable`, in any case; an alarm webhook ID cannot be `.` or `..` |
|
|
172
|
+
|
|
173
|
+
A call that leaves out `state` or `enable`, or passes a malformed ID, fails input validation. A call without `confirm: true` returns an error result ("This action requires confirmation: …"), so the agent can ask you and call again.
|
|
174
|
+
|
|
175
|
+
Toggling was removed because it is not safe to retry: a repeated toggle flips a gate or lock back to where it started. To act on the current state, read it first: each entry in `outputs` from `protect_get_relay` carries a `state`, and `protect_get_alarm_hub` reports `alarmHub.output` keyed by channel.
|
|
109
176
|
|
|
110
|
-
|
|
177
|
+
```jsonc
|
|
178
|
+
// 2.x: toggles output 0
|
|
179
|
+
{ "id": "<relay id>", "outputId": 0 }
|
|
180
|
+
// 3.0: explicit, so a repeat leaves the output as asked (a pulseDuration pulses again)
|
|
181
|
+
{ "id": "<relay id>", "outputId": 0, "state": "on", "confirm": true }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Relaxed:** a `dryRun: true` preview never needs `confirm`. In 2.x, `protect_disable_mic` and `protect_trigger_alarm_webhook` rejected a dry run without `confirm: true`. Calls that send both still work.
|
|
185
|
+
|
|
186
|
+
**New:** every list tool except `protect_list_nvrs` takes optional `limit` and `offset`. A call with neither returns the whole list, as before.
|
|
187
|
+
|
|
188
|
+
### Annotation changes (breaking for allow-lists)
|
|
189
|
+
|
|
190
|
+
- `protect_update_camera`, every `protect_update_<device>` tool, `protect_update_arm_profile` and `protect_update_liveview` are now `destructiveHint: true`, and so is every tool that takes `confirm` (`protect_stop_siren`, `protect_set_current_arm_profile`, `protect_create_rtsp_stream`, `protect_create_talkback`, `protect_disable_arm_alarm`, `protect_activate_relay_output`, `protect_trigger_alarm_hub_output` among them). MCP clients that auto-approve non-destructive tools will now ask before running them, and allow-lists keyed on the annotation need updating.
|
|
191
|
+
- Every tool now has a `title` and `openWorldHint: false`.
|
|
192
|
+
|
|
193
|
+
### Stricter validation
|
|
194
|
+
|
|
195
|
+
- **`UNIFI_PROTECT_HOST`** is parsed as a URL authority. A pasted `https://192.168.1.1/` now works, and `:443` is dropped. A path, credentials, a non-numeric or out-of-range port, or an unbracketed IPv6 address now fails at startup instead of at the first request. Write IPv6 as `[2001:db8::1]` or `[2001:db8::1]:7443`.
|
|
196
|
+
- **`protect_upload_file`** rejects `base64Data` that is not valid base64 (bad characters, length or padding), and empty input, where 2.x uploaded corrupt bytes without an error. A `data:` URL prefix and line breaks are accepted. A file too large to fit one MCP message is refused with "File too large to send in one MCP message"; in 2.x an oversized request closed the server's stdio transport and the server exited with nothing logged.
|
|
197
|
+
- **`protect_update_liveview`** rejects a body whose `settings.id` differs from `id`.
|
|
198
|
+
|
|
199
|
+
### Output changes
|
|
200
|
+
|
|
201
|
+
- Text results are compact JSON, not indented. `structuredContent` is unchanged; use it if you parse results.
|
|
202
|
+
- Single-object results and dry-run previews now pass MCP clients' output validation. In 2.x, a top-level field the schema did not list, or any dry-run preview of a tool with an output schema, failed with `-32602`.
|
|
203
|
+
- A snapshot, response or other result too large for one MCP message is refused with an error saying so. In 2.x it was sent, and the MCP client dropped the connection.
|
|
204
|
+
- The `protect_upload_file` dry-run preview reports `byteLength` (decoded size) in place of `dataLength` (base64 string length).
|
|
205
|
+
- `protect_subscribe_devices` / `protect_subscribe_events` stop at a message cap **or** a size budget, whichever comes first (both stated in each tool's description), and add `truncated: true`. `duration` is the time actually spent listening. A listen that fails before any message arrives (refused or unauthorized socket) returns an **error result**; in 2.x it returned `{messages: [], error}` as a success. One that fails partway still returns what it collected, with `error`.
|
|
206
|
+
- A timeout names the call and what to do: `PATCH /cameras/…: Protect did not respond within … s`, followed by "Retry when the console responds" for reads, or "The change may still have been applied; check before retrying" for writes. Snapshots and uploads time out before the MCP client's own request timeout, so you see this message instead of a bare client timeout.
|
|
207
|
+
- Network and TLS errors name the underlying cause, e.g. `fetch failed: connect ECONNREFUSED …`, instead of a bare `fetch failed`.
|
|
208
|
+
- A subscription that the console refuses, closes or drops before any message arrives returns an error naming why, instead of an empty list that reads as a quiet site. So does one whose window ends before the connection is established, or whose first message is too large to return.
|
|
209
|
+
- A list tool reads a response of up to several times one MCP message and returns the page asked for, so `limit` and `offset` work on a site whose full list would not fit.
|
|
210
|
+
- Cancelling a tool call aborts its request to Protect, and a cancelled subscription closes its socket. In 2.x a cancelled relay switch could still land afterwards.
|
|
211
|
+
- The server reports its real package version to MCP clients; 2.x always reported `1.0.0`. It also sends server instructions (see [Safety Features](#safety-features)).
|
|
212
|
+
|
|
213
|
+
### For packagers
|
|
214
|
+
|
|
215
|
+
`undici` is a new runtime dependency (scoped TLS and connection keep-alive). Requests go through undici's own `fetch` rather than Node's, so:
|
|
216
|
+
|
|
217
|
+
- They speak HTTP/1.1, as before, and send `User-Agent: unifi-protect-mcp/<version>`.
|
|
218
|
+
- An HTTP proxy is used as Node's built-in `fetch` would: from `HTTPS_PROXY` / `HTTP_PROXY`, minus `NO_PROXY`, only when `NODE_USE_ENV_PROXY=1` or `--use-env-proxy` is set. A dispatcher installed with undici's `setGlobalDispatcher` no longer applies. The subscription WebSocket never used a proxy, and still does not.
|
|
219
|
+
|
|
220
|
+
## Safety Features
|
|
111
221
|
|
|
112
|
-
|
|
222
|
+
- **Tool annotations.** Every tool declares `readOnlyHint`, `destructiveHint` and `openWorldHint: false` (each talks only to your console), plus a readable `title`, so MCP clients such as Claude Code can decide when to ask you. `destructiveHint: true` marks every tool that deletes or replaces existing state, which includes every `protect_update_*` tool, and every tool that takes `confirm`. Clients that auto-approve non-destructive tools ask before those. The tool tables below mark them **DESTRUCTIVE**.
|
|
223
|
+
- **Server instructions.** On connect, the server gives the MCP host a short guide for the model, matched to read-only or read-write mode. It says to treat names and event text from the console as data, not instructions; that RTSPS and talkback URLs are credentials; and, when writes are enabled, to preview with `dryRun` and ask you before passing `confirm`.
|
|
224
|
+
- **Read-only mode.** On by default. Only the read tools (list, get, snapshot, subscriptions) are registered; write tools do not exist for the model, and registering one in read-only mode fails at startup. Set `UNIFI_PROTECT_READ_ONLY=false` to enable writes. Read-only mode still returns RTSPS URLs from `protect_get_rtsp_streams`, and those are credentials for live video.
|
|
225
|
+
- **Confirmation parameter.** Tools that are irreversible, switch physical outputs, trigger the alarm webhook, stop a siren, disarm or weaken alarm coverage, or open live video or two-way audio refuse to act unless the call carries `confirm: true`. Without it the tool returns an error naming the action, so the agent can ask you first. `protect_update_sensor`, `protect_update_fob`, `protect_update_siren` and `protect_update_arm_profile` need it only when the change touches alarm-related settings (each tool's description names them); renames and LED changes do not. `confirm` is filled in by the model, so it is a prompt for the model to ask you, not a lock; for a human gate, keep your MCP client's approval prompts on for destructive tools.
|
|
226
|
+
- **Dry-run support.** Every write tool accepts `dryRun: true` and returns the request it would send (`dryRun`, `action`, `path`, `body`) without changing anything. A dry run never needs `confirm`.
|
|
227
|
+
- **Request safety.** IDs must be IDs returned by list and get tools, and a request path that would resolve outside the Integration API is refused before it is sent. Redirects are refused, so the API key is never forwarded. Cancelling a tool call aborts its request to Protect.
|
|
228
|
+
|
|
229
|
+
See [SECURITY.md](SECURITY.md) for reporting vulnerabilities and operating the server safely.
|
|
230
|
+
|
|
231
|
+
## Structured output
|
|
232
|
+
|
|
233
|
+
Read tools, and write tools that return the resource, declare an `outputSchema`, and return `structuredContent` alongside compact JSON text. Schemas are published as loose objects, so a field a firmware update adds flows through instead of failing validation. A list's items are under `result`; a paged list also has `totalCount` and `offset`. `protect_get_snapshot` returns an image. A dry run returns `{dryRun, action, path, body}`. Action endpoints that return no body succeed with an empty result.
|
|
234
|
+
|
|
235
|
+
## Tools
|
|
236
|
+
|
|
237
|
+
**DESTRUCTIVE** marks tools annotated `destructiveHint: true`. *Requires `confirm: true`* marks tools that take `confirm`; every one of them is also DESTRUCTIVE. *Pages with `limit`/`offset`* marks list tools that take them.
|
|
238
|
+
|
|
239
|
+
### System
|
|
113
240
|
| Tool | Description |
|
|
114
241
|
|---|---|
|
|
115
242
|
| `protect_get_info` | Get system information and version details |
|
|
116
|
-
| `protect_list_nvrs` |
|
|
243
|
+
| `protect_list_nvrs` | Get the NVR console info (a single object) |
|
|
117
244
|
|
|
118
|
-
### Subscriptions
|
|
245
|
+
### Subscriptions
|
|
119
246
|
| Tool | Description |
|
|
120
247
|
|---|---|
|
|
121
|
-
| `protect_subscribe_devices` | Subscribe via WebSocket to device state updates |
|
|
122
|
-
| `protect_subscribe_events` | Subscribe via WebSocket to event notifications |
|
|
248
|
+
| `protect_subscribe_devices` | Subscribe via WebSocket to device state updates for a short window (not over Cloud Connector) |
|
|
249
|
+
| `protect_subscribe_events` | Subscribe via WebSocket to event notifications for a short window (not over Cloud Connector) |
|
|
123
250
|
|
|
124
|
-
### Cameras
|
|
251
|
+
### Cameras
|
|
125
252
|
| Tool | Description |
|
|
126
253
|
|---|---|
|
|
127
|
-
| `protect_list_cameras` | List all cameras |
|
|
254
|
+
| `protect_list_cameras` | List all cameras (pages with `limit`/`offset`) |
|
|
128
255
|
| `protect_get_camera` | Get camera details by ID |
|
|
129
|
-
| `protect_update_camera` | Update camera settings |
|
|
256
|
+
| `protect_update_camera` | **DESTRUCTIVE:** Update camera settings |
|
|
130
257
|
| `protect_get_snapshot` | Get a JPEG snapshot (returns image) |
|
|
131
|
-
| `protect_create_rtsp_stream` | Create an RTSPS stream session |
|
|
132
|
-
| `protect_get_rtsp_streams` | Get active RTSPS stream sessions |
|
|
133
|
-
| `protect_delete_rtsp_stream` | Stop and delete an active RTSPS stream |
|
|
134
|
-
| `protect_create_talkback` | Create a talkback (two-way audio) session |
|
|
135
|
-
| `protect_disable_mic` | **
|
|
258
|
+
| `protect_create_rtsp_stream` | **DESTRUCTIVE:** Create an RTSPS stream session; the returned URLs are credentials for live video (requires `confirm: true`) |
|
|
259
|
+
| `protect_get_rtsp_streams` | Get active RTSPS stream sessions (the URLs are credentials for live video) |
|
|
260
|
+
| `protect_delete_rtsp_stream` | **DESTRUCTIVE:** Stop and delete an active RTSPS stream |
|
|
261
|
+
| `protect_create_talkback` | **DESTRUCTIVE:** Create a talkback (two-way audio) session; the returned URL is a credential (requires `confirm: true`) |
|
|
262
|
+
| `protect_disable_mic` | **DESTRUCTIVE:** Irreversibly disable the camera microphone; only a factory reset restores it (requires `confirm: true`) |
|
|
136
263
|
| `protect_start_ptz_patrol` | Start PTZ patrol at a given slot |
|
|
137
264
|
| `protect_stop_ptz_patrol` | Stop PTZ patrol |
|
|
138
265
|
| `protect_goto_ptz_preset` | Move PTZ to a preset position |
|
|
139
266
|
|
|
140
|
-
### Lights
|
|
267
|
+
### Lights
|
|
141
268
|
| Tool | Description |
|
|
142
269
|
|---|---|
|
|
143
|
-
| `protect_list_lights` | List all lights |
|
|
270
|
+
| `protect_list_lights` | List all lights (pages with `limit`/`offset`) |
|
|
144
271
|
| `protect_get_light` | Get light details by ID |
|
|
145
|
-
| `protect_update_light` | Update light settings |
|
|
272
|
+
| `protect_update_light` | **DESTRUCTIVE:** Update light settings |
|
|
146
273
|
|
|
147
|
-
### Sensors
|
|
274
|
+
### Sensors
|
|
148
275
|
| Tool | Description |
|
|
149
276
|
|---|---|
|
|
150
|
-
| `protect_list_sensors` | List all sensors |
|
|
277
|
+
| `protect_list_sensors` | List all sensors (pages with `limit`/`offset`) |
|
|
151
278
|
| `protect_get_sensor` | Get sensor details by ID |
|
|
152
|
-
| `protect_update_sensor` | Update sensor settings |
|
|
279
|
+
| `protect_update_sensor` | **DESTRUCTIVE:** Update sensor settings (requires `confirm: true` to change alarm, motion or glass-break settings, arm profiles or schedule mode) |
|
|
153
280
|
|
|
154
|
-
### Chimes
|
|
281
|
+
### Chimes
|
|
155
282
|
| Tool | Description |
|
|
156
283
|
|---|---|
|
|
157
|
-
| `protect_list_chimes` | List all chimes |
|
|
284
|
+
| `protect_list_chimes` | List all chimes (pages with `limit`/`offset`) |
|
|
158
285
|
| `protect_get_chime` | Get chime details by ID |
|
|
159
|
-
| `protect_update_chime` | Update chime settings |
|
|
286
|
+
| `protect_update_chime` | **DESTRUCTIVE:** Update chime settings |
|
|
160
287
|
|
|
161
|
-
### Viewers
|
|
288
|
+
### Viewers
|
|
162
289
|
| Tool | Description |
|
|
163
290
|
|---|---|
|
|
164
|
-
| `protect_list_viewers` | List all viewers |
|
|
291
|
+
| `protect_list_viewers` | List all viewers (pages with `limit`/`offset`) |
|
|
165
292
|
| `protect_get_viewer` | Get viewer details by ID |
|
|
166
|
-
| `protect_update_viewer` | Update viewer settings |
|
|
293
|
+
| `protect_update_viewer` | **DESTRUCTIVE:** Update viewer settings |
|
|
167
294
|
|
|
168
|
-
### Sirens
|
|
295
|
+
### Sirens
|
|
169
296
|
| Tool | Description |
|
|
170
297
|
|---|---|
|
|
171
|
-
| `protect_list_sirens` | List all sirens |
|
|
298
|
+
| `protect_list_sirens` | List all sirens (pages with `limit`/`offset`) |
|
|
172
299
|
| `protect_get_siren` | Get siren details by ID |
|
|
173
|
-
| `protect_update_siren` | Update siren settings
|
|
300
|
+
| `protect_update_siren` | **DESTRUCTIVE:** Update siren settings: name, volume, LED (requires `confirm: true` to change the volume) |
|
|
174
301
|
| `protect_play_siren` | Activate the siren alarm for a given duration (5/10/20/30s) |
|
|
175
|
-
| `protect_stop_siren` | Stop an active siren |
|
|
302
|
+
| `protect_stop_siren` | **DESTRUCTIVE:** Stop an active siren; it silences an alarm (requires `confirm: true`) |
|
|
176
303
|
| `protect_test_siren_sound` | Test the siren sound for 5 seconds at a given volume |
|
|
177
304
|
|
|
178
|
-
### Fobs
|
|
305
|
+
### Fobs
|
|
179
306
|
| Tool | Description |
|
|
180
307
|
|---|---|
|
|
181
|
-
| `protect_list_fobs` | List all key fobs |
|
|
308
|
+
| `protect_list_fobs` | List all key fobs (pages with `limit`/`offset`) |
|
|
182
309
|
| `protect_get_fob` | Get fob details by ID |
|
|
183
|
-
| `protect_update_fob` | Update fob settings |
|
|
310
|
+
| `protect_update_fob` | **DESTRUCTIVE:** Update fob settings (requires `confirm: true` to change arm control settings) |
|
|
184
311
|
|
|
185
|
-
### Relays
|
|
312
|
+
### Relays
|
|
186
313
|
| Tool | Description |
|
|
187
314
|
|---|---|
|
|
188
|
-
| `protect_list_relays` | List all relays |
|
|
189
|
-
| `protect_get_relay` | Get relay details by ID |
|
|
190
|
-
| `protect_update_relay` | Update relay settings |
|
|
191
|
-
| `protect_activate_relay_output` |
|
|
315
|
+
| `protect_list_relays` | List all relays (pages with `limit`/`offset`) |
|
|
316
|
+
| `protect_get_relay` | Get relay details by ID, including each output's state |
|
|
317
|
+
| `protect_update_relay` | **DESTRUCTIVE:** Update relay settings |
|
|
318
|
+
| `protect_activate_relay_output` | **DESTRUCTIVE:** Turn a relay output channel on or off, with optional pulse duration (requires `confirm: true` and an explicit `state`) |
|
|
192
319
|
|
|
193
|
-
### Speakers
|
|
320
|
+
### Speakers
|
|
194
321
|
| Tool | Description |
|
|
195
322
|
|---|---|
|
|
196
|
-
| `protect_list_speakers` | List all speakers |
|
|
323
|
+
| `protect_list_speakers` | List all speakers (pages with `limit`/`offset`) |
|
|
197
324
|
| `protect_get_speaker` | Get speaker details by ID |
|
|
198
|
-
| `protect_update_speaker` | Update speaker settings (volume, mic) |
|
|
325
|
+
| `protect_update_speaker` | **DESTRUCTIVE:** Update speaker settings (volume, mic) |
|
|
199
326
|
| `protect_test_speaker_sound` | Test the speaker sound at a given volume |
|
|
200
327
|
|
|
201
|
-
### Bridges
|
|
328
|
+
### Bridges
|
|
202
329
|
| Tool | Description |
|
|
203
330
|
|---|---|
|
|
204
|
-
| `protect_list_bridges` | List all bridges |
|
|
331
|
+
| `protect_list_bridges` | List all bridges (pages with `limit`/`offset`) |
|
|
205
332
|
| `protect_get_bridge` | Get bridge details by ID |
|
|
206
|
-
| `protect_update_bridge` | Update bridge settings |
|
|
333
|
+
| `protect_update_bridge` | **DESTRUCTIVE:** Update bridge settings |
|
|
207
334
|
|
|
208
|
-
### Link Stations
|
|
335
|
+
### Link Stations
|
|
209
336
|
| Tool | Description |
|
|
210
337
|
|---|---|
|
|
211
|
-
| `protect_list_link_stations` | List all link stations
|
|
338
|
+
| `protect_list_link_stations` | List all link stations, the non-alarm-hub gateways (pages with `limit`/`offset`) |
|
|
212
339
|
| `protect_get_link_station` | Get link station details by ID |
|
|
213
|
-
| `protect_update_link_station` | Update link station settings |
|
|
340
|
+
| `protect_update_link_station` | **DESTRUCTIVE:** Update link station settings |
|
|
214
341
|
|
|
215
|
-
### Alarm Hubs
|
|
342
|
+
### Alarm Hubs
|
|
216
343
|
| Tool | Description |
|
|
217
344
|
|---|---|
|
|
218
|
-
| `protect_list_alarm_hubs` | List all alarm hubs |
|
|
345
|
+
| `protect_list_alarm_hubs` | List all alarm hubs (pages with `limit`/`offset`) |
|
|
219
346
|
| `protect_get_alarm_hub` | Get alarm hub details by ID |
|
|
220
|
-
| `protect_update_alarm_hub` | Update alarm hub settings |
|
|
221
|
-
| `protect_trigger_alarm_hub_output` |
|
|
347
|
+
| `protect_update_alarm_hub` | **DESTRUCTIVE:** Update alarm hub settings |
|
|
348
|
+
| `protect_trigger_alarm_hub_output` | **DESTRUCTIVE:** Turn an alarm hub output channel on or off: sirens, lights, locks (requires `confirm: true` and an explicit `enable`) |
|
|
222
349
|
|
|
223
|
-
### Arm Profiles
|
|
350
|
+
### Arm Profiles — local alarm manager
|
|
224
351
|
| Tool | Description |
|
|
225
352
|
|---|---|
|
|
226
|
-
| `protect_list_arm_profiles` | List all arm profiles |
|
|
353
|
+
| `protect_list_arm_profiles` | List all arm profiles (pages with `limit`/`offset`) |
|
|
227
354
|
| `protect_create_arm_profile` | Create a new arm profile |
|
|
228
|
-
| `protect_set_current_arm_profile` | Set the active profile used when arming |
|
|
229
|
-
| `protect_update_arm_profile` | Update an arm profile |
|
|
230
|
-
| `protect_delete_arm_profile` | **DESTRUCTIVE:** Delete an arm profile by ID |
|
|
355
|
+
| `protect_set_current_arm_profile` | **DESTRUCTIVE:** Set the active profile used when arming (requires `confirm: true`) |
|
|
356
|
+
| `protect_update_arm_profile` | **DESTRUCTIVE:** Update an arm profile (requires `confirm: true` to change schedules, automations, activation delay or recording) |
|
|
357
|
+
| `protect_delete_arm_profile` | **DESTRUCTIVE:** Delete an arm profile by ID (requires `confirm: true`) |
|
|
231
358
|
| `protect_enable_arm_alarm` | Arm the alarm using the current profile |
|
|
232
|
-
| `protect_disable_arm_alarm` | Disarm the alarm |
|
|
359
|
+
| `protect_disable_arm_alarm` | **DESTRUCTIVE:** Disarm the alarm (requires `confirm: true`) |
|
|
233
360
|
|
|
234
|
-
### Live Views
|
|
361
|
+
### Live Views
|
|
235
362
|
| Tool | Description |
|
|
236
363
|
|---|---|
|
|
237
|
-
| `protect_list_liveviews` | List all live views |
|
|
364
|
+
| `protect_list_liveviews` | List all live views (pages with `limit`/`offset`) |
|
|
238
365
|
| `protect_get_liveview` | Get live view details by ID |
|
|
239
366
|
| `protect_create_liveview` | Create a new live view |
|
|
240
|
-
| `protect_update_liveview` |
|
|
367
|
+
| `protect_update_liveview` | **DESTRUCTIVE:** Replace a live view (send the full object from `protect_get_liveview`) |
|
|
241
368
|
|
|
242
|
-
### Alarm & Files
|
|
369
|
+
### Alarm & Files
|
|
243
370
|
| Tool | Description |
|
|
244
371
|
|---|---|
|
|
245
|
-
| `protect_trigger_alarm_webhook` | Trigger an alarm webhook
|
|
246
|
-
| `protect_list_files` | List device asset files (`animations`) |
|
|
372
|
+
| `protect_trigger_alarm_webhook` | **DESTRUCTIVE:** Trigger an alarm webhook, firing its external alarm action (requires `confirm: true`) |
|
|
373
|
+
| `protect_list_files` | List device asset files (`animations`; pages with `limit`/`offset`) |
|
|
247
374
|
| `protect_upload_file` | Upload a device asset file (base64 in, sent as multipart/form-data) |
|
|
248
375
|
|
|
249
|
-
### Users
|
|
376
|
+
### Users
|
|
250
377
|
| Tool | Description |
|
|
251
378
|
|---|---|
|
|
252
|
-
| `protect_list_users` | List Protect users
|
|
379
|
+
| `protect_list_users` | List Protect users, filtered by the key's access (pages with `limit`/`offset`) |
|
|
253
380
|
| `protect_get_user` | Get a Protect user by ID |
|
|
254
|
-
| `protect_list_ulp_users` | List UniFi Identity (ULP) users with enrolled credentials |
|
|
381
|
+
| `protect_list_ulp_users` | List UniFi Identity (ULP) users with enrolled credentials (pages with `limit`/`offset`) |
|
|
255
382
|
| `protect_get_ulp_user` | Get a UniFi Identity user by ID |
|
|
256
383
|
|
|
257
|
-
### Point of Sale
|
|
384
|
+
### Point of Sale
|
|
258
385
|
| Tool | Description |
|
|
259
386
|
|---|---|
|
|
260
387
|
| `protect_ingest_pos_transaction` | Record a POS transaction as a camera event so it can be overlaid on recorded footage |
|
|
261
388
|
|
|
389
|
+
## Troubleshooting
|
|
390
|
+
|
|
391
|
+
Configuration errors go to the MCP host's server log and the server exits. Tool errors reach the agent as results starting with `Error:`.
|
|
392
|
+
|
|
393
|
+
| Message | Cause and fix |
|
|
394
|
+
|---|---|
|
|
395
|
+
| `Configuration error: UNIFI_PROTECT_HOST is required` / `UNIFI_PROTECT_API_KEY is required` | Missing from the server's `env`. |
|
|
396
|
+
| `UNIFI_PROTECT_HOST must be a hostname or IP …` | A path, credentials or a bad port in the host; bracket IPv6 addresses. |
|
|
397
|
+
| `UNIFI_PROTECT_API_KEY contains a control character …` | Paste the key again. |
|
|
398
|
+
| `cannot read UNIFI_PROTECT_CA_CERT (…)` | The path does not exist for the server. Use an absolute path; `~` is not expanded. |
|
|
399
|
+
| `UNIFI_PROTECT_CA_CERT must name a PEM file containing a certificate` | No `BEGIN CERTIFICATE` block; run the `openssl` command again. |
|
|
400
|
+
| `…cannot be combined with UNIFI_PROTECT_VERIFY_SSL=false` / `TLS verification cannot be disabled for api.ui.com` / `UNIFI_PROTECT_CA_CERT is for local consoles` | Unset `UNIFI_PROTECT_VERIFY_SSL` (or `UNIFI_PROTECT_CA_CERT` for `api.ui.com`). |
|
|
401
|
+
| `Ignoring UNIFI_PROTECT_CONSOLE_ID` (log line) | The console ID only applies with `UNIFI_PROTECT_HOST=api.ui.com`. |
|
|
402
|
+
| `… (read-only: write tools are not registered)` (log line) and no write tools | Read-only is the default. Set `UNIFI_PROTECT_READ_ONLY=false` and restart the host. |
|
|
403
|
+
| `fetch failed: … The console's TLS certificate could not be verified …` | Save the console's certificate to `UNIFI_PROTECT_CA_CERT`. If you already did, the console has a new certificate: save it again. |
|
|
404
|
+
| `fetch failed: … The certificate for api.ui.com could not be verified …` | Something between you and Ubiquiti's cloud is presenting another certificate, such as a TLS-inspecting proxy or firewall. |
|
|
405
|
+
| `fetch failed: connect ECONNREFUSED …`, `ENOTFOUND` | Wrong or unreachable host. |
|
|
406
|
+
| `HTTP 401` / `HTTP 403` | Wrong or revoked key, or its admin lacks the rights. |
|
|
407
|
+
| `… Protect did not respond within … s` | The console is slow. For a write, check whether it was applied before retrying. |
|
|
408
|
+
| `… exceeds the …-byte limit` / `Result too large to send in one MCP message …` | The response is too large for one MCP message. Page a list with `limit` and `offset`; for a snapshot, drop `highQuality`. |
|
|
409
|
+
| `File too large to send in one MCP message` | Upload a smaller file. |
|
|
410
|
+
| `This action requires confirmation: …` | Expected: the action needs `confirm: true`, and the agent should ask you first. |
|
|
411
|
+
| `Use an ID returned by a list or get tool` | The agent passed a name or a malformed ID. |
|
|
412
|
+
| `Subscription failed: …` | The WebSocket was refused or unauthorized; this is not "no activity". |
|
|
413
|
+
| `WebSocket subscriptions are not available over Cloud Connector …` | Connect to the console directly for subscriptions. |
|
|
414
|
+
| `MCP error -32602 … Input validation error` | The call broke the tool's input schema (for example a missing `state`); the message names the field. |
|
|
415
|
+
|
|
262
416
|
## Development
|
|
263
417
|
|
|
264
418
|
The Node version used for development is pinned in `.nvmrc`. With [fnm](https://github.com/Schniz/fnm) installed, `fnm use` reads it automatically on `cd`.
|
|
@@ -266,32 +420,28 @@ The Node version used for development is pinned in `.nvmrc`. With [fnm](https://
|
|
|
266
420
|
```bash
|
|
267
421
|
pnpm run build # Compile TypeScript
|
|
268
422
|
pnpm start # Run the server
|
|
269
|
-
pnpm run typecheck # Type-check without emitting
|
|
270
|
-
pnpm run lint # ESLint
|
|
423
|
+
pnpm run typecheck # Type-check src/ and tests/ without emitting
|
|
424
|
+
pnpm run lint # ESLint, type-aware
|
|
271
425
|
pnpm run lint:fix # ESLint with auto-fix
|
|
272
426
|
pnpm test # Run all tests (vitest)
|
|
273
427
|
pnpm run test:watch # Run tests in watch mode
|
|
274
428
|
pnpm run test:coverage # Run tests with coverage
|
|
429
|
+
pnpm run test:package # Pack, install the tarball and smoke-test it over stdio
|
|
275
430
|
```
|
|
276
431
|
|
|
277
|
-
Git hooks are managed by [lefthook](https://github.com/evilmartians/lefthook) — `pnpm exec lefthook install` enables them. Pre-commit runs ESLint on staged files
|
|
432
|
+
Git hooks are managed by [lefthook](https://github.com/evilmartians/lefthook) — `pnpm exec lefthook install` enables them. Pre-commit runs ESLint on staged files, a full typecheck, and a GitGuardian secret scan when [ggshield](https://github.com/GitGuardian/ggshield) is installed (skipped otherwise), so fnm and pnpm both need to be on your PATH.
|
|
278
433
|
|
|
279
434
|
### Commit conventions
|
|
280
435
|
|
|
281
|
-
This project uses [conventional commits](https://www.conventionalcommits.org/) and [release-
|
|
436
|
+
This project uses [conventional commits](https://www.conventionalcommits.org/) in pull request titles and [release-drafter](https://github.com/release-drafter/release-drafter) for releases. Pull requests are squash-merged, so the title is the commit subject:
|
|
282
437
|
|
|
283
438
|
- `feat: ...` — new feature (minor version bump)
|
|
284
439
|
- `fix: ...` — bug fix (patch version bump)
|
|
285
|
-
- `
|
|
440
|
+
- `deps: ...` — dependency update (patch version bump; Renovate's type for package bumps)
|
|
441
|
+
- `feat!: ...` — breaking change (major version bump); the `!` must be in the title, a `BREAKING CHANGE:` footer is not read
|
|
286
442
|
- `chore:`, `docs:`, `ci:`, etc. — no version bump
|
|
287
443
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
To override the version number, add `Release-As: x.x.x` in the commit body:
|
|
291
|
-
|
|
292
|
-
```bash
|
|
293
|
-
git commit --allow-empty -m "chore: release 2.0.0" -m "Release-As: 2.0.0"
|
|
294
|
-
```
|
|
444
|
+
Every push to `main` updates a draft GitHub Release with the next version and its notes. A maintainer publishes the draft, which builds, tests and publishes that tagged commit to npm with provenance. To release a different version, edit the draft's tag and title before publishing. Release notes after 3.0.0 are in [GitHub Releases](https://github.com/owine/unifi-protect-mcp/releases); `CHANGELOG.md` covers 3.0.0 and earlier.
|
|
295
445
|
|
|
296
446
|
## License
|
|
297
447
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,19 +1,51 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import { Config } from "./config.js";
|
|
3
|
+
/** Run fn so that every request it makes is aborted when signal is. */
|
|
4
|
+
export declare function withCallSignal<T>(signal: AbortSignal | undefined, fn: () => T): T;
|
|
5
|
+
/** The abort signal of the tool call in progress, if any. */
|
|
6
|
+
export declare function currentCallSignal(): AbortSignal | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* The reason an error carries, for a message the model reads. A connection
|
|
9
|
+
* that tried several addresses (a name with both IPv4 and IPv6 records) fails
|
|
10
|
+
* with an AggregateError whose own message is empty; its reasons are in
|
|
11
|
+
* `errors`, and its code in `code`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function errorReason(err: Error): string;
|
|
3
14
|
export declare class ProtectClient {
|
|
4
15
|
private baseUrl;
|
|
16
|
+
private basePath;
|
|
17
|
+
private tls;
|
|
18
|
+
private dispatcher;
|
|
5
19
|
private headers;
|
|
6
20
|
private usingConnector;
|
|
7
21
|
constructor(config: Config);
|
|
22
|
+
/** Every header but Content-Type, for a request whose body sets its own. */
|
|
23
|
+
private authHeaders;
|
|
24
|
+
/**
|
|
25
|
+
* Model-supplied IDs become path segments. safePath encodes `/ ? # % \`, but
|
|
26
|
+
* a bare `.` or `..` survives encoding and URL parsing then folds it out of
|
|
27
|
+
* the path, so the request would reach a different endpoint than the tool
|
|
28
|
+
* built. Refuse any path that parsing would rewrite. The ID schemas reject
|
|
29
|
+
* these first; this is the backstop for a tool that forgets one.
|
|
30
|
+
*/
|
|
31
|
+
private resolve;
|
|
32
|
+
/**
|
|
33
|
+
* The one place a request is sent: every path builds its URL, applies
|
|
34
|
+
* `redirect: "error"` (so X-API-KEY never follows a redirect) and its
|
|
35
|
+
* timeout, and turns a non-2xx status into an error with a bounded body.
|
|
36
|
+
*/
|
|
37
|
+
private send;
|
|
8
38
|
private request;
|
|
39
|
+
private sendJson;
|
|
9
40
|
get(path: string): Promise<unknown>;
|
|
10
41
|
post(path: string, body?: unknown): Promise<unknown>;
|
|
11
42
|
patch(path: string, body: unknown): Promise<unknown>;
|
|
12
43
|
delete(path: string): Promise<unknown>;
|
|
13
|
-
getBinary(path: string): Promise<{
|
|
44
|
+
getBinary(path: string, maxBytes?: number): Promise<{
|
|
14
45
|
data: Buffer;
|
|
15
46
|
mimeType: string;
|
|
16
47
|
}>;
|
|
48
|
+
private download;
|
|
17
49
|
/**
|
|
18
50
|
* Upload a file as multipart/form-data (the documented encoding for
|
|
19
51
|
* POST /files/{fileType}). fetch sets the Content-Type with its boundary, so
|
|
@@ -21,5 +53,7 @@ export declare class ProtectClient {
|
|
|
21
53
|
* was verified against a live 7.3.70 console.
|
|
22
54
|
*/
|
|
23
55
|
postFile(path: string, data: Buffer, contentType: string, fileName: string): Promise<unknown>;
|
|
24
|
-
connectWebSocket(path: string
|
|
56
|
+
connectWebSocket(path: string, options?: {
|
|
57
|
+
maxPayload?: number;
|
|
58
|
+
}): WebSocket;
|
|
25
59
|
}
|