@mgcrea/mcp-unifi-protect 0.2.0 → 0.3.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 +170 -3
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +91 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-B2-0Akro.js → server-B1wSHfi7.js} +1262 -433
- package/dist/server-B1wSHfi7.js.map +1 -0
- package/package.json +5 -2
- package/dist/server-B2-0Akro.js.map +0 -1
package/README.md
CHANGED
|
@@ -19,6 +19,97 @@ at all unless you ask for them.
|
|
|
19
19
|
- **Stays up with no credentials**, reporting what to configure through `unifi_protect_auth_status`
|
|
20
20
|
rather than exiting and showing in your client as a bare `Connection closed`.
|
|
21
21
|
|
|
22
|
+
## Two ways to connect
|
|
23
|
+
|
|
24
|
+
| | `local` (default) | `cloud` |
|
|
25
|
+
| --------------------- | -------------------------------------------- | --------------------------------------- |
|
|
26
|
+
| Reaches the console | directly on your LAN | via `api.ui.com` Site Manager connector |
|
|
27
|
+
| Credentials | host + username + password | API key + console id |
|
|
28
|
+
| Auth mechanism | UniFi OS login → session cookie + CSRF token | `X-API-KEY` header |
|
|
29
|
+
| TLS | console's self-signed cert — needs setup | a real certificate, nothing to do |
|
|
30
|
+
| Works off-LAN | no | yes |
|
|
31
|
+
| Session state on disk | yes, mode `600` | none |
|
|
32
|
+
|
|
33
|
+
**Both modes expose exactly the same tools**, because both speak the same private
|
|
34
|
+
Protect API — the connector forwards the whole `/proxy/protect/...` tree, the private
|
|
35
|
+
API included. That is not obvious and is worth stating plainly: Ubiquiti's _official_
|
|
36
|
+
Integration API has no historical query capability whatsoever, so if the connector only
|
|
37
|
+
carried that, cloud mode could not answer a single question about the past. It carries
|
|
38
|
+
the private API too, verified against a live console — `bootstrap`, `events`, `cameras`
|
|
39
|
+
and binary snapshots all answer `200`.
|
|
40
|
+
|
|
41
|
+
So cloud mode is a full alternative, not a reduced one, and it removes the local
|
|
42
|
+
account, the password, the session file, the CSRF handshake and the self-signed
|
|
43
|
+
certificate problem in one go.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# cloud — no local account at all
|
|
47
|
+
UNIFI_PROTECT_API_KEY=… # unifi.ui.com → Settings → API Keys
|
|
48
|
+
UNIFI_PROTECT_CONSOLE_ID=… # curl -H "X-API-KEY: $KEY" https://api.ui.com/v1/hosts
|
|
49
|
+
|
|
50
|
+
# local — on the LAN
|
|
51
|
+
UNIFI_PROTECT_HOST=192.168.1.1
|
|
52
|
+
UNIFI_PROTECT_USERNAME=mcp
|
|
53
|
+
UNIFI_PROTECT_PASSWORD=…
|
|
54
|
+
UNIFI_PROTECT_VERIFY_TLS=false
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`UNIFI_PROTECT_MODE` is inferred as `cloud` when an API key and a console id are both
|
|
58
|
+
set, so it usually needs no setting. `console`/`unifios`/`lan` and
|
|
59
|
+
`remote`/`site-manager`/`connector` are accepted as synonyms, and an unrecognised value
|
|
60
|
+
is reported through `unifi_protect_auth_status` rather than killing the server.
|
|
61
|
+
|
|
62
|
+
**Two traps in cloud mode.** A key that works for `/v1/hosts` can still return
|
|
63
|
+
`403 user cannot access host in the organization` for a console outside the
|
|
64
|
+
organization it was issued in — valid key, wrong org, and the message reads nothing like
|
|
65
|
+
a credentials problem. And API keys are **per-console**: a key created on your Network
|
|
66
|
+
gateway or a UNAS does not authenticate against the NVR running Protect, and the NVR
|
|
67
|
+
rejects it exactly as it rejects a made-up key.
|
|
68
|
+
|
|
69
|
+
### Why local mode needs a username and password
|
|
70
|
+
|
|
71
|
+
An API key looks like it ought to work here, and it is the obvious thing to reach for.
|
|
72
|
+
It does not, and the reason is worth writing down so nobody spends an afternoon on it.
|
|
73
|
+
|
|
74
|
+
A key created **on the console itself** (UniFi OS → Control Plane → Integrations) is
|
|
75
|
+
recognised — but only by Ubiquiti's _official_ Integration API. It is refused by the
|
|
76
|
+
private API this server depends on. Tested against a UNVR on Protect 7.2.105 with a key
|
|
77
|
+
issued on that console:
|
|
78
|
+
|
|
79
|
+
| Endpoint | With a console API key |
|
|
80
|
+
| ----------------------------------------- | ---------------------- |
|
|
81
|
+
| `/proxy/protect/integration/v1/meta/info` | `200` |
|
|
82
|
+
| `/proxy/protect/integration/v1/cameras` | `200` |
|
|
83
|
+
| `/proxy/protect/integration/v1/nvrs` | `200` |
|
|
84
|
+
| `/proxy/protect/api/nvr` | **`401`** |
|
|
85
|
+
| `/proxy/protect/api/cameras` | **`401`** |
|
|
86
|
+
| `/proxy/protect/api/events` | **`401`** |
|
|
87
|
+
| `/proxy/protect/api/bootstrap` | **`500`** |
|
|
88
|
+
|
|
89
|
+
A fabricated key returns `401` on the official API too, so the `200`s above confirm the
|
|
90
|
+
key really was valid — the private API simply does not accept key auth.
|
|
91
|
+
|
|
92
|
+
Putting all three paths together:
|
|
93
|
+
|
|
94
|
+
| Path | Authenticates with | Private API (event history, snapshots) |
|
|
95
|
+
| ----------------------------- | --------------------- | -------------------------------------- |
|
|
96
|
+
| `local` + username / password | session cookie + CSRF | ✅ |
|
|
97
|
+
| `local` + API key | `X-API-KEY` | ❌ `401` |
|
|
98
|
+
| `cloud` + API key | `X-API-KEY` | ✅ |
|
|
99
|
+
|
|
100
|
+
The asymmetry is not arbitrary. Over the connector, `api.ui.com` authenticates _you_ by
|
|
101
|
+
key and then reaches the console over its own trusted channel, so the console is never
|
|
102
|
+
asked to accept a key on a private path. On the LAN there is no such intermediary, and
|
|
103
|
+
the private API only knows the session the web app itself uses.
|
|
104
|
+
|
|
105
|
+
**So a local-only deployment needs a username and password.** That is a property of
|
|
106
|
+
Protect, not a shortcut taken here. Use a dedicated Local-Access-Only account with
|
|
107
|
+
View Only rights, as described below, and the credential's blast radius stays small.
|
|
108
|
+
|
|
109
|
+
The one thing a console API key _would_ unlock is the PTZ move commands
|
|
110
|
+
(`ptz/goto`, `ptz/patrol/start`, `ptz/patrol/stop`), which exist only on the official
|
|
111
|
+
Integration API — see [Not implemented](#not-implemented).
|
|
112
|
+
|
|
22
113
|
## Security
|
|
23
114
|
|
|
24
115
|
**Supply chain.** Three runtime dependencies: the MCP SDK, zod, and `undici`. Retry and backoff
|
|
@@ -117,7 +208,7 @@ printf '%s\n' \
|
|
|
117
208
|
|
|
118
209
|
## Tools
|
|
119
210
|
|
|
120
|
-
|
|
211
|
+
22 read tools, plus 10 more when writes are enabled.
|
|
121
212
|
|
|
122
213
|
| Tool | What it does | Writes |
|
|
123
214
|
| ----------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------- |
|
|
@@ -130,9 +221,11 @@ printf '%s\n' \
|
|
|
130
221
|
| `unifi_protect_get_camera_snapshot` | Capture a frame now, to a file or inline | — |
|
|
131
222
|
| `unifi_protect_list_ptz_presets` | A PTZ camera's saved preset slots | — |
|
|
132
223
|
| `unifi_protect_list_ptz_patrols` | A PTZ camera's saved patrol routes | — |
|
|
224
|
+
| `unifi_protect_check_settings` | **Audit every camera for inconsistent or self-defeating settings** | — |
|
|
133
225
|
| `unifi_protect_list_events` | **Search recorded events over any time range** | — |
|
|
134
226
|
| `unifi_protect_get_event` | One event's full detection metadata | — |
|
|
135
227
|
| `unifi_protect_get_event_thumbnail` | The frame that triggered a detection | — |
|
|
228
|
+
| `unifi_protect_get_event_thumbnails` | Up to 6 frames at once, inline — how you tell a person from a branch | — |
|
|
136
229
|
| `unifi_protect_export_video` | Export footage as an MP4 on disk | — |
|
|
137
230
|
| `unifi_protect_list_lights` | Floodlights, with state and brightness | — |
|
|
138
231
|
| `unifi_protect_list_sensors` | Sensors, with temperature / humidity / light readings | — |
|
|
@@ -142,6 +235,7 @@ printf '%s\n' \
|
|
|
142
235
|
| `unifi_protect_list_users` | Who can sign in to Protect | — |
|
|
143
236
|
| `unifi_protect_request` | Escape hatch: call any private endpoint directly | GET only unless writes |
|
|
144
237
|
| `unifi_protect_update_camera` | Name, mic, status LED, OSD overlays | ✅ |
|
|
238
|
+
| `unifi_protect_set_camera_detections` | Which objects and sounds a camera detects — the gate below | ✅ |
|
|
145
239
|
| `unifi_protect_set_camera_recording_mode` | `always` / `never` / `detections` / `schedule` | ✅ |
|
|
146
240
|
| `unifi_protect_reboot_camera` | Reboot one camera | ✅ confirm |
|
|
147
241
|
| `unifi_protect_update_light` | Brightness, on/off, PIR sensitivity | ✅ |
|
|
@@ -151,6 +245,24 @@ printf '%s\n' \
|
|
|
151
245
|
| `unifi_protect_update_nvr_settings` | Console name, timezone, global recording | ✅ |
|
|
152
246
|
| `unifi_protect_reboot_nvr` | Reboot the console | ✅ confirm |
|
|
153
247
|
|
|
248
|
+
## Resources and prompts
|
|
249
|
+
|
|
250
|
+
Three resources carry the standing facts a question needs before a tool is chosen, so a client
|
|
251
|
+
can attach them once instead of spending a call per question:
|
|
252
|
+
|
|
253
|
+
| Resource | Why it exists |
|
|
254
|
+
| --------------------------- | ---------------------------------------------------------------------------------------- |
|
|
255
|
+
| `unifi-protect://console` | The console's **time zone**, so "1am" is read as the local clock rather than UTC |
|
|
256
|
+
| `unifi-protect://cameras` | What each camera will **actually** detect, what its zones ask for, and where they differ |
|
|
257
|
+
| `unifi-protect://locations` | Named groups of cameras, so a question about a _place_ resolves to ids |
|
|
258
|
+
|
|
259
|
+
Two prompts carry the procedure, which is the part a tool list cannot express:
|
|
260
|
+
|
|
261
|
+
- **`check_camera_settings`** — run the audit and interpret it, changing nothing. Several
|
|
262
|
+
findings have two valid opposite fixes, and which is right depends on what the camera is for.
|
|
263
|
+
- **`who_passed`** — find who was present in a window, and **fall back to motion frames on any
|
|
264
|
+
camera whose detector is off** rather than reporting a zero count as an absence.
|
|
265
|
+
|
|
154
266
|
## Worked example: what happened at the front door last night
|
|
155
267
|
|
|
156
268
|
```jsonc
|
|
@@ -205,12 +317,46 @@ console, and both now handled in either form:
|
|
|
205
317
|
results report `hasThumbnail: true` rather than an id, and `unifi_protect_get_event_thumbnail`
|
|
206
318
|
takes the event's `id` (though it tolerates an `e-…` value too).
|
|
207
319
|
|
|
320
|
+
**Smart detection is gated in two places, and only one of them is obvious.**
|
|
321
|
+
`smartDetectSettings.objectTypes` on the device is the master switch;
|
|
322
|
+
`smartDetectZones[].objectTypes` says what each zone asks for. A zone can ask for `person` while
|
|
323
|
+
the device list omits it, and the console then reports nothing at all — no error, no warning,
|
|
324
|
+
just an empty result forever. On the console this was built against, a doorbell had
|
|
325
|
+
`zone: [person, vehicle, animal]` against `device: [animal]`, so a person search returned zero
|
|
326
|
+
across seven days while people walked past nightly.
|
|
327
|
+
|
|
328
|
+
Zero results are therefore never reported bare. `unifi_protect_list_events` cross-checks the
|
|
329
|
+
requested detection types against each camera's device list and returns a `warnings` array
|
|
330
|
+
saying the detector was off — the difference between "nobody was there" and "nothing was
|
|
331
|
+
looking". `unifi_protect_check_settings` finds the same misconfiguration across the whole
|
|
332
|
+
system, and `unifi_protect_set_camera_detections` fixes it, keeping the zones in step.
|
|
333
|
+
|
|
334
|
+
One limit worth knowing: the check reflects the camera's setting **now**, so a historical search
|
|
335
|
+
over a period when the detector was off but has since been enabled gets no warning.
|
|
336
|
+
|
|
337
|
+
**Some settings are reported on read but refused on write.** `smartDetectSettings.audioTypes`
|
|
338
|
+
comes back containing `smoke_cmonx`, and a PATCH containing it fails with
|
|
339
|
+
`400 The smart detection feature is not enabled for: smoke_cmonx`. Any read-modify-write that
|
|
340
|
+
echoes the list back therefore breaks. `unifi_protect_set_camera_detections` filters against
|
|
341
|
+
`featureFlags.smartDetectAudioTypes` and reports what it dropped.
|
|
342
|
+
|
|
343
|
+
**Camera filtering happens on the console, and the parameter must be repeated.** `/events`
|
|
344
|
+
accepts `cameras=<id>`, repeated once per camera. A comma-separated list is accepted and
|
|
345
|
+
silently matches nothing. This mattered more than it looks: filtering client-side instead
|
|
346
|
+
fetches the newest `limit` events across _all_ cameras and discards the rest, so a quiet camera
|
|
347
|
+
over a long window came back empty while reporting a successful search.
|
|
348
|
+
|
|
208
349
|
**Times are milliseconds, and getting it wrong fails silently.** The console takes JavaScript
|
|
209
350
|
millisecond timestamps. A Unix _seconds_ value is not rejected — it is read as a moment in 1970,
|
|
210
351
|
so the query succeeds and returns an empty list, which reads as "nothing happened". Every time
|
|
211
352
|
argument here accepts ISO 8601, a relative expression (`"2h ago"`, `"30m"`, `"7d"`) or `"now"`,
|
|
212
353
|
and a ten-digit number is refused with the corrected value in the error.
|
|
213
354
|
|
|
355
|
+
Local forms are also accepted — `"1am"`, `"01:30"`, `"2026-08-30 01:00"` — and read in the
|
|
356
|
+
**console's own time zone**, because a question about last night is a question about the clock
|
|
357
|
+
where the cameras are. A bare time of day resolves to its most recent occurrence, and `start`
|
|
358
|
+
anchors to the window's end, so "1am to 6am" stays one coherent night however late it is asked.
|
|
359
|
+
|
|
214
360
|
**Event search is always filtered by type.** Omitting `types` entirely triggers a pagination bug
|
|
215
361
|
in Protect where the console ignores the window and returns the wrong slice. `unifi_protect_list_events`
|
|
216
362
|
always sends an explicit list, defaulting to motion, smart detections and rings.
|
|
@@ -240,6 +386,22 @@ whereas a refused one invites an agent to keep trying.
|
|
|
240
386
|
**`self-signed certificate` errors.** Verification is on by default and cannot pass against an IP address. Either address the console by name with `NODE_EXTRA_CA_CERTS` set, or `UNIFI_PROTECT_VERIFY_TLS=false`
|
|
241
387
|
unless you have installed a trusted certificate on the console.
|
|
242
388
|
|
|
389
|
+
**Cloud mode returns 403 `user cannot access host in the organization`.** The key is
|
|
390
|
+
valid but was issued in an organization that does not contain that console. Check the
|
|
391
|
+
console appears in `curl -H "X-API-KEY: $KEY" https://api.ui.com/v1/hosts`; if the web
|
|
392
|
+
dashboard shows it but that call does not, they are different organizations.
|
|
393
|
+
|
|
394
|
+
**I set an API key for local mode and everything returns 401.** Local mode cannot use an
|
|
395
|
+
API key — see [Why local mode needs a username and password](#why-local-mode-needs-a-username-and-password).
|
|
396
|
+
Set `UNIFI_PROTECT_USERNAME` and `UNIFI_PROTECT_PASSWORD`, or switch to `cloud` mode,
|
|
397
|
+
where a key is all you need.
|
|
398
|
+
|
|
399
|
+
**A local API key returns 401 on everything.** API keys are per-console. A key created
|
|
400
|
+
on your Network gateway is not valid on the NVR running Protect — and the NVR rejects an
|
|
401
|
+
unknown key with exactly the same `401` it gives a fabricated one, so the message cannot
|
|
402
|
+
distinguish "wrong console" from "wrong key". Create the key on the console you are
|
|
403
|
+
addressing, or use cloud mode.
|
|
404
|
+
|
|
243
405
|
**Everything returns 401.** Check the account is a local one, and that it has Protect
|
|
244
406
|
permissions. `unifi_protect_auth_status` distinguishes "cannot log in" from "logged in but
|
|
245
407
|
forbidden".
|
|
@@ -255,7 +417,12 @@ so inline so it does not read as a fault.
|
|
|
255
417
|
|
|
256
418
|
## What has been verified against real hardware
|
|
257
419
|
|
|
258
|
-
|
|
420
|
+
**Cloud mode** was verified end-to-end against a live console over the Site Manager
|
|
421
|
+
connector: `auth_status` reachable, camera list, and event search returning real
|
|
422
|
+
detections with their camera names resolved — all authenticated by an API key alone,
|
|
423
|
+
with no local account anywhere in the picture.
|
|
424
|
+
|
|
425
|
+
**Local mode** was built and exercised end-to-end against a live **UNVR4 on Protect 7.2.105** with 12 cameras, one
|
|
259
426
|
floodlight and one chime. Every read tool was run; the write tools were exercised with _no-op_
|
|
260
427
|
writes — each value set to the value it already held — and the device state read back unchanged
|
|
261
428
|
afterwards. That run is also what caught three bugs this README's earlier drafts described
|
|
@@ -291,7 +458,7 @@ pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build
|
|
|
291
458
|
Publish:
|
|
292
459
|
|
|
293
460
|
```bash
|
|
294
|
-
pnpm
|
|
461
|
+
pnpm release minor # bump, commit, tag (patch|minor|major)
|
|
295
462
|
git push --follow-tags # CI publishes to npm + GHCR from the tag
|
|
296
463
|
```
|
|
297
464
|
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { G as BUILD_INFO, L as setupInstructions, M as isConfigured, N as loadConfig, i as createServer } from "./server-B1wSHfi7.js";
|
|
3
3
|
import { ZodError } from "zod";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
//#region src/cli.ts
|
|
@@ -27,7 +27,8 @@ const main = async () => {
|
|
|
27
27
|
});
|
|
28
28
|
const transport = new StdioServerTransport();
|
|
29
29
|
await server.connect(transport);
|
|
30
|
-
stderrLogger.warn(`unifi-protect-mcp connected (host=${config.baseUrl ?? "MISSING"}, user=${config.username ?? "MISSING"}, writes=${config.allowWrites ? "ENABLED" : "disabled"}, tls=${config.verifyTls ? "verified" : "UNVERIFIED"})`);
|
|
30
|
+
stderrLogger.warn(`unifi-protect-mcp connected (mode=${config.mode}, ` + (config.mode === "cloud" ? `console=${config.consoleId ?? "MISSING"}, auth=${config.apiKey ? "api-key" : "MISSING"}, ` : `host=${config.baseUrl ?? "MISSING"}, user=${config.username ?? "MISSING"}, `) + `writes=${config.allowWrites ? "ENABLED" : "disabled"}, tls=${config.mode === "cloud" || config.verifyTls ? "verified" : "UNVERIFIED"})`);
|
|
31
|
+
for (const issue of config.issues) stderrLogger.warn(` ${issue}`);
|
|
31
32
|
if (!isConfigured(config)) {
|
|
32
33
|
stderrLogger.warn(" not configured — only unifi_protect_auth_status is available:");
|
|
33
34
|
for (const line of setupInstructions(config)) stderrLogger.warn(` ${line}`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { ZodError } from \"zod\";\n\nimport { BUILD_INFO } from \"
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { ZodError } from \"zod\";\n\nimport { BUILD_INFO } from \"#/build-info\";\nimport { isConfigured, loadConfig, setupInstructions } from \"#/config\";\nimport { createServer } from \"#/server\";\n\n// Everything goes to stderr: stdout is the MCP protocol channel, and a stray\n// log line there corrupts the JSON-RPC stream — usually failing the client's\n// next parse, far from the cause.\n// oxlint-disable no-console -- this is the process entry point; stderr is the log channel.\nconst stderrLogger = {\n debug: (...args: unknown[]) => {\n if (process.env.UNIFI_PROTECT_DEBUG) console.error(\"[unifi-protect-mcp]\", ...args);\n },\n warn: (...args: unknown[]) => console.error(\"[unifi-protect-mcp]\", ...args),\n error: (...args: unknown[]) => console.error(\"[unifi-protect-mcp]\", ...args),\n};\n\n/** Show a config mistake as its field messages, not 40 frames of zod internals. */\nconst describeFatal = (err: unknown): string => {\n if (err instanceof ZodError) {\n return err.issues\n .map((issue) => {\n const path = issue.path.join(\".\");\n return path ? `${path}: ${issue.message}` : issue.message;\n })\n .join(\"\\n\");\n }\n return err instanceof Error ? err.message : String(err);\n};\n\nconst main = async (): Promise<void> => {\n stderrLogger.warn(\n `${BUILD_INFO.name}@${BUILD_INFO.version} (git ${BUILD_INFO.gitCommit} ${BUILD_INFO.gitCommitDate}, node ${process.version})`,\n );\n\n const config = loadConfig();\n // Before anything can open a socket.\n\n const { server } = createServer({ config, logger: stderrLogger });\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n stderrLogger.warn(\n `unifi-protect-mcp connected (mode=${config.mode}, ` +\n (config.mode === \"cloud\"\n ? `console=${config.consoleId ?? \"MISSING\"}, auth=${config.apiKey ? \"api-key\" : \"MISSING\"}, `\n : `host=${config.baseUrl ?? \"MISSING\"}, user=${config.username ?? \"MISSING\"}, `) +\n `writes=${config.allowWrites ? \"ENABLED\" : \"disabled\"}, ` +\n `tls=${config.mode === \"cloud\" || config.verifyTls ? \"verified\" : \"UNVERIFIED\"})`,\n );\n\n // Connecting successfully but exposing one tool is confusing unless we say\n // why. The server no longer refuses to start over this, so the banner and\n // unifi_protect_auth_status are the only channels left.\n for (const issue of config.issues) stderrLogger.warn(` ${issue}`);\n\n if (!isConfigured(config)) {\n stderrLogger.warn(\" not configured — only unifi_protect_auth_status is available:\");\n for (const line of setupInstructions(config)) stderrLogger.warn(` ${line}`);\n stderrLogger.warn(\" Call unifi_protect_auth_status for this same guidance in your client.\");\n }\n\n const shutdown = (signal: string): void => {\n stderrLogger.warn(`received ${signal}, shutting down`);\n process.exit(0);\n };\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n};\n\nmain().catch((err: unknown) => {\n console.error(`[unifi-protect-mcp] fatal: ${describeFatal(err)}`);\n if (process.env.UNIFI_PROTECT_DEBUG && err instanceof Error) console.error(err.stack);\n process.exit(1);\n});\n"],"mappings":";;;;;AAYA,MAAM,eAAe;CACnB,QAAQ,GAAG,SAAoB;EAC7B,IAAI,QAAQ,IAAI,qBAAqB,QAAQ,MAAM,uBAAuB,GAAG,IAAI;CACnF;CACA,OAAO,GAAG,SAAoB,QAAQ,MAAM,uBAAuB,GAAG,IAAI;CAC1E,QAAQ,GAAG,SAAoB,QAAQ,MAAM,uBAAuB,GAAG,IAAI;AAC7E;;AAGA,MAAM,iBAAiB,QAAyB;CAC9C,IAAI,eAAe,UACjB,OAAO,IAAI,OACR,KAAK,UAAU;EACd,MAAM,OAAO,MAAM,KAAK,KAAK,GAAG;EAChC,OAAO,OAAO,GAAG,KAAK,IAAI,MAAM,YAAY,MAAM;CACpD,CAAC,CAAC,CACD,KAAK,IAAI;CAEd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,MAAM,OAAO,YAA2B;CACtC,aAAa,KACX,GAAG,WAAW,KAAK,GAAG,WAAW,QAAQ,QAAQ,WAAW,UAAU,GAAG,WAAW,cAAc,SAAS,QAAQ,QAAQ,EAC7H;CAEA,MAAM,SAAS,WAAW;CAG1B,MAAM,EAAE,WAAW,aAAa;EAAE;EAAQ,QAAQ;CAAa,CAAC;CAChE,MAAM,YAAY,IAAI,qBAAqB;CAC3C,MAAM,OAAO,QAAQ,SAAS;CAE9B,aAAa,KACX,qCAAqC,OAAO,KAAK,OAC9C,OAAO,SAAS,UACb,WAAW,OAAO,aAAa,UAAU,SAAS,OAAO,SAAS,YAAY,UAAU,MACxF,QAAQ,OAAO,WAAW,UAAU,SAAS,OAAO,YAAY,UAAU,OAC9E,UAAU,OAAO,cAAc,YAAY,WAAW,QAC/C,OAAO,SAAS,WAAW,OAAO,YAAY,aAAa,aAAa,EACnF;CAKA,KAAK,MAAM,SAAS,OAAO,QAAQ,aAAa,KAAK,KAAK,OAAO;CAEjE,IAAI,CAAC,aAAa,MAAM,GAAG;EACzB,aAAa,KAAK,iEAAiE;EACnF,KAAK,MAAM,QAAQ,kBAAkB,MAAM,GAAG,aAAa,KAAK,KAAK,MAAM;EAC3E,aAAa,KAAK,yEAAyE;CAC7F;CAEA,MAAM,YAAY,WAAyB;EACzC,aAAa,KAAK,YAAY,OAAO,gBAAgB;EACrD,QAAQ,KAAK,CAAC;CAChB;CACA,QAAQ,GAAG,gBAAgB,SAAS,QAAQ,CAAC;CAC7C,QAAQ,GAAG,iBAAiB,SAAS,SAAS,CAAC;AACjD;AAEA,KAAK,CAAC,CAAC,OAAO,QAAiB;CAC7B,QAAQ,MAAM,8BAA8B,cAAc,GAAG,GAAG;CAChE,IAAI,QAAQ,IAAI,uBAAuB,eAAe,OAAO,QAAQ,MAAM,IAAI,KAAK;CACpF,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,19 @@ declare const LOGIN_PATH = "/api/auth/login";
|
|
|
20
20
|
/** The realtime channel. Not used yet — see the WebSocket note in the README. */
|
|
21
21
|
declare const UPDATES_WS_PATH = "/proxy/protect/ws/updates";
|
|
22
22
|
declare const ConfigSchema: z.ZodObject<{
|
|
23
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
24
|
+
cloud: "cloud";
|
|
25
|
+
local: "local";
|
|
26
|
+
}>>;
|
|
27
|
+
modeSource: z.ZodDefault<z.ZodEnum<{
|
|
28
|
+
default: "default";
|
|
29
|
+
explicit: "explicit";
|
|
30
|
+
inferred: "inferred";
|
|
31
|
+
invalid: "invalid";
|
|
32
|
+
}>>;
|
|
23
33
|
baseUrl: z.ZodOptional<z.ZodString>;
|
|
34
|
+
consoleId: z.ZodOptional<z.ZodString>;
|
|
35
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
24
36
|
username: z.ZodOptional<z.ZodString>;
|
|
25
37
|
password: z.ZodOptional<z.ZodString>;
|
|
26
38
|
totp: z.ZodOptional<z.ZodString>;
|
|
@@ -31,6 +43,9 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
31
43
|
maxRetries: z.ZodDefault<z.ZodNumber>;
|
|
32
44
|
maxDownloadBytes: z.ZodDefault<z.ZodNumber>;
|
|
33
45
|
deviceCacheTtlSeconds: z.ZodDefault<z.ZodNumber>;
|
|
46
|
+
timeZone: z.ZodOptional<z.ZodString>;
|
|
47
|
+
locations: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
48
|
+
issues: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
34
49
|
}, z.core.$strict>;
|
|
35
50
|
type Config = z.infer<typeof ConfigSchema>;
|
|
36
51
|
/**
|
|
@@ -42,7 +57,13 @@ type Config = z.infer<typeof ConfigSchema>;
|
|
|
42
57
|
* which is the worst way to learn your credentials came from somewhere else.
|
|
43
58
|
*/
|
|
44
59
|
declare const FileConfigSchema: z.ZodObject<{
|
|
60
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
cloud: "cloud";
|
|
62
|
+
local: "local";
|
|
63
|
+
}>>;
|
|
45
64
|
host: z.ZodOptional<z.ZodString>;
|
|
65
|
+
consoleId: z.ZodOptional<z.ZodString>;
|
|
66
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
46
67
|
username: z.ZodOptional<z.ZodString>;
|
|
47
68
|
password: z.ZodOptional<z.ZodString>;
|
|
48
69
|
verifyTls: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -52,6 +73,8 @@ declare const FileConfigSchema: z.ZodObject<{
|
|
|
52
73
|
maxRetries: z.ZodOptional<z.ZodNumber>;
|
|
53
74
|
maxDownloadBytes: z.ZodOptional<z.ZodNumber>;
|
|
54
75
|
deviceCacheTtlSeconds: z.ZodOptional<z.ZodNumber>;
|
|
76
|
+
timeZone: z.ZodOptional<z.ZodString>;
|
|
77
|
+
locations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
55
78
|
}, z.core.$strict>;
|
|
56
79
|
type FileConfig = z.infer<typeof FileConfigSchema>;
|
|
57
80
|
/**
|
|
@@ -79,12 +102,6 @@ declare const expandTilde: (path: string) => string;
|
|
|
79
102
|
declare const resolveConfigPath: (env?: NodeJS.ProcessEnv) => string;
|
|
80
103
|
/** The session file sits beside the config file unless told otherwise. */
|
|
81
104
|
declare const resolveSessionPath: (env?: NodeJS.ProcessEnv) => string;
|
|
82
|
-
/**
|
|
83
|
-
* Environment first, config file second, **per field** — not whole-source.
|
|
84
|
-
* Docker and CI inject the environment and must keep working untouched, while a
|
|
85
|
-
* one-off `UNIFI_PROTECT_ALLOW_WRITES=0` still has to override a file that says
|
|
86
|
-
* `true`. Merging field by field is the only rule that gives both.
|
|
87
|
-
*/
|
|
88
105
|
declare const loadConfig: (env?: NodeJS.ProcessEnv, configPath?: string) => Config;
|
|
89
106
|
/** True once the server has everything it needs to reach a console. */
|
|
90
107
|
declare const isConfigured: (config: Config) => boolean;
|
|
@@ -102,14 +119,17 @@ type Logger = {
|
|
|
102
119
|
error?(...args: unknown[]): void;
|
|
103
120
|
};
|
|
104
121
|
/** The two headers that authenticate every request to a UniFi OS console. */
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
/**
|
|
123
|
+
* The headers that authenticate one request. Deliberately an open record rather
|
|
124
|
+
* than the cookie/CSRF pair it used to be: local mode sends
|
|
125
|
+
* `cookie` + `x-csrf-token`, cloud mode sends a single `x-api-key`, and the
|
|
126
|
+
* client should not have to know which it is holding.
|
|
127
|
+
*/
|
|
128
|
+
type SessionHeaders = Record<string, string>;
|
|
109
129
|
type SessionStatus = {
|
|
110
130
|
authenticated: boolean;
|
|
111
131
|
/** Where the live session came from, for unifi_protect_auth_status. */
|
|
112
|
-
source: "none" | "restored" | "login";
|
|
132
|
+
source: "none" | "restored" | "login" | "api-key";
|
|
113
133
|
username: string | undefined;
|
|
114
134
|
savedAt: string | undefined;
|
|
115
135
|
};
|
|
@@ -137,6 +157,28 @@ declare const createSessionProvider: (opts: SessionProviderOptions) => SessionPr
|
|
|
137
157
|
/** For tests: fixed headers, no network, no disk. */
|
|
138
158
|
declare const staticSessionProvider: (headers?: SessionHeaders) => SessionProvider;
|
|
139
159
|
//#endregion
|
|
160
|
+
//#region src/client/detection.d.ts
|
|
161
|
+
/** Everything about one camera that decides whether a detection could occur. */
|
|
162
|
+
type CameraFacts = {
|
|
163
|
+
id: string;
|
|
164
|
+
name: string;
|
|
165
|
+
/** The master switch: `smartDetectSettings.objectTypes`. */
|
|
166
|
+
enabled: string[];
|
|
167
|
+
/** The union of what the zones ask for. */
|
|
168
|
+
zone: string[];
|
|
169
|
+
/** What the hardware supports: `featureFlags.smartDetectTypes`. */
|
|
170
|
+
capable: string[];
|
|
171
|
+
audioEnabled: string[];
|
|
172
|
+
audioCapable: string[];
|
|
173
|
+
hasSmartDetect: boolean;
|
|
174
|
+
isConnected: boolean;
|
|
175
|
+
recordingMode: string | undefined;
|
|
176
|
+
motionDetectionEnabled: boolean | undefined;
|
|
177
|
+
/** Types a zone asks for that the device list blocks — the silent failure. */
|
|
178
|
+
blocked: string[];
|
|
179
|
+
};
|
|
180
|
+
type CameraIndex = ReadonlyMap<string, CameraFacts>;
|
|
181
|
+
//#endregion
|
|
140
182
|
//#region src/client/protect.d.ts
|
|
141
183
|
/** Array values become repeated params, which is how Protect expects `types`. */
|
|
142
184
|
type Query = Record<string, string | number | boolean | string[] | undefined>;
|
|
@@ -221,7 +263,7 @@ declare const summarizeCamera: (camera: Rec) => Rec;
|
|
|
221
263
|
* would otherwise have to perform against a separate camera list, and get
|
|
222
264
|
* silently wrong. The id is kept too, since the write and snapshot tools need it.
|
|
223
265
|
*/
|
|
224
|
-
declare const summarizeEvent: (event: Rec, cameras?: NameIndex) => Rec;
|
|
266
|
+
declare const summarizeEvent: (event: Rec, cameras?: NameIndex, timeZone?: string) => Rec;
|
|
225
267
|
declare const summarizeLight: (light: Rec) => Rec;
|
|
226
268
|
declare const summarizeSensor: (sensor: Rec) => Rec;
|
|
227
269
|
declare const summarizeViewer: (viewer: Rec) => Rec;
|
|
@@ -244,17 +286,32 @@ type DeviceCacheOptions = {
|
|
|
244
286
|
now?: () => number;
|
|
245
287
|
};
|
|
246
288
|
/**
|
|
247
|
-
* A short-lived
|
|
248
|
-
* every event into something readable
|
|
289
|
+
* A short-lived index of the cameras, used to resolve the `camera` reference on
|
|
290
|
+
* every event into something readable, and to explain why a detection search
|
|
291
|
+
* could not have matched.
|
|
249
292
|
*
|
|
250
293
|
* It is cached because event search is the hot path and every result set needs
|
|
251
294
|
* the same index: fetching the camera list once per search rather than once per
|
|
252
295
|
* event is the difference between one extra request and none. The TTL is short
|
|
253
296
|
* because a renamed or newly adopted camera should appear without a restart,
|
|
254
297
|
* and a stale name is only ever cosmetic — the id travels alongside it.
|
|
298
|
+
*
|
|
299
|
+
* Names and detection facts come from ONE fetch. They were two calls until the
|
|
300
|
+
* gate cross-check needed both on the same path, and a second round-trip per
|
|
301
|
+
* search to re-read the list we already had is pure waste.
|
|
255
302
|
*/
|
|
256
303
|
type DeviceCache = {
|
|
304
|
+
/** id → display name, for resolving event references. */
|
|
257
305
|
cameras(): Promise<NameIndex>;
|
|
306
|
+
/** id → everything that decides whether a detection could occur. */
|
|
307
|
+
facts(): Promise<CameraIndex>;
|
|
308
|
+
/**
|
|
309
|
+
* The console's IANA time zone, or undefined if it cannot be read.
|
|
310
|
+
*
|
|
311
|
+
* Needed to interpret "1am" as the person asking meant it. Cached far longer
|
|
312
|
+
* than the camera list: a console's zone changes when someone moves house.
|
|
313
|
+
*/
|
|
314
|
+
timeZone(): Promise<string | undefined>;
|
|
258
315
|
invalidate(): void;
|
|
259
316
|
};
|
|
260
317
|
declare const createDeviceCache: (opts: DeviceCacheOptions) => DeviceCache;
|
|
@@ -367,6 +424,21 @@ declare const registerTools: (server: McpServer, client: ProtectClient, ctx: Too
|
|
|
367
424
|
declare const assertSafePath: (path: string) => void;
|
|
368
425
|
//#endregion
|
|
369
426
|
//#region src/tools/util.d.ts
|
|
427
|
+
type TimeOptions = {
|
|
428
|
+
now?: number;
|
|
429
|
+
/**
|
|
430
|
+
* The console's time zone. A bare "1am" means 1am WHERE THE CAMERAS ARE, not
|
|
431
|
+
* wherever this process happens to run, and an hour's error silently returns
|
|
432
|
+
* the wrong night's footage.
|
|
433
|
+
*/
|
|
434
|
+
timeZone?: string;
|
|
435
|
+
/**
|
|
436
|
+
* A bare time-of-day resolves to its latest occurrence at or before this.
|
|
437
|
+
* Event search passes the window's end here, so "1am to 6am" asked at 03:00
|
|
438
|
+
* still resolves to one coherent night rather than an inverted window.
|
|
439
|
+
*/
|
|
440
|
+
before?: number;
|
|
441
|
+
};
|
|
370
442
|
/**
|
|
371
443
|
* Convert a time expression to milliseconds since the Unix epoch.
|
|
372
444
|
*
|
|
@@ -377,9 +449,12 @@ declare const assertSafePath: (path: string) => void;
|
|
|
377
449
|
* night", which is the most expensive possible failure for this server.
|
|
378
450
|
*
|
|
379
451
|
* Accepts ISO 8601, a relative expression like "2h ago" or "30m", the literal
|
|
380
|
-
* "now",
|
|
452
|
+
* "now", a raw millisecond number, and — when a `timeZone` is supplied — naive
|
|
453
|
+
* local forms: "2026-08-30 01:00", "2026-08-30", "01:00" and "1am". Naive forms
|
|
454
|
+
* are read in the console's zone, because that is the zone the person asking is
|
|
455
|
+
* thinking in.
|
|
381
456
|
*/
|
|
382
|
-
declare const toEpochMs: (value: string | number,
|
|
457
|
+
declare const toEpochMs: (value: string | number, opts?: number | TimeOptions) => number;
|
|
383
458
|
//#endregion
|
|
384
459
|
export { type BinaryResult, type Config, type CreateServerOptions, type CreatedServer, type DeviceCache, type FileConfig, LOGIN_PATH, type Logger, type NameIndex, NotConfiguredError, PRIVATE_API_PATH, type PersistedSession, ProtectApiError, ProtectAuthError, ProtectClient, type ProtectClientOptions, type Query, SERVER_NAME, SERVER_VERSION, type SessionHeaders, type SessionProvider, type SessionStatus, type ToolContext, UPDATES_WS_PATH, USER_AGENT, WritesDisabledError, assertSafePath, backoffMs, buildNameIndex, buildQuery, clearSession, createDeviceCache, createServer, createSessionProvider, expandTilde, isConfigured, isoTime, loadConfig, loadSession, normalizeBaseUrl, registerTools, resolveConfigPath, resolveSessionPath, retryAfterMs, saveSession, setupInstructions, staticSessionProvider, summarizeBootstrap, summarizeCamera, summarizeChime, summarizeEach, summarizeEvent, summarizeLight, summarizeLiveview, summarizeNvr, summarizeSensor, summarizeUser, summarizeViewer, toEpochMs };
|
|
385
460
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/config.ts","../src/client/auth.ts","../src/client/protect.ts","../src/client/shape.ts","../src/client/device-cache.ts","../src/server.ts","../src/client/session-store.ts","../src/client/errors.ts","../src/tools/index.ts","../src/tools/request.ts","../src/tools/util.ts"],"mappings":";;;;;;;;;;;;;;;;cAmBa;;cAGA;;cAGA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/config.ts","../src/client/auth.ts","../src/client/detection.ts","../src/client/protect.ts","../src/client/shape.ts","../src/client/device-cache.ts","../src/server.ts","../src/client/session-store.ts","../src/client/errors.ts","../src/tools/index.ts","../src/tools/request.ts","../src/tools/util.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as UPDATES_WS_PATH, B as saveSession, C as buildQuery, D as staticSessionProvider, E as createSessionProvider, F as resolveConfigPath, H as ProtectApiError, I as resolveSessionPath, L as setupInstructions, M as isConfigured, N as loadConfig, O as LOGIN_PATH, P as normalizeBaseUrl, R as clearSession, S as backoffMs, T as createDeviceCache, U as ProtectAuthError, V as NotConfiguredError, W as WritesDisabledError, _ as summarizeNvr, a as registerTools, b as summarizeViewer, c as buildNameIndex, d as summarizeCamera, f as summarizeChime, g as summarizeLiveview, h as summarizeLight, i as createServer, j as expandTilde, k as PRIVATE_API_PATH, l as isoTime, m as summarizeEvent, n as SERVER_VERSION, o as assertSafePath, p as summarizeEach, r as USER_AGENT, s as toEpochMs, t as SERVER_NAME, u as summarizeBootstrap, v as summarizeSensor, w as retryAfterMs, x as ProtectClient, y as summarizeUser, z as loadSession } from "./server-B1wSHfi7.js";
|
|
2
2
|
export { LOGIN_PATH, NotConfiguredError, PRIVATE_API_PATH, ProtectApiError, ProtectAuthError, ProtectClient, SERVER_NAME, SERVER_VERSION, UPDATES_WS_PATH, USER_AGENT, WritesDisabledError, assertSafePath, backoffMs, buildNameIndex, buildQuery, clearSession, createDeviceCache, createServer, createSessionProvider, expandTilde, isConfigured, isoTime, loadConfig, loadSession, normalizeBaseUrl, registerTools, resolveConfigPath, resolveSessionPath, retryAfterMs, saveSession, setupInstructions, staticSessionProvider, summarizeBootstrap, summarizeCamera, summarizeChime, summarizeEach, summarizeEvent, summarizeLight, summarizeLiveview, summarizeNvr, summarizeSensor, summarizeUser, summarizeViewer, toEpochMs };
|