@owine/unifi-protect-mcp 2.14.2 → 3.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.
Files changed (60) hide show
  1. package/README.md +248 -92
  2. package/dist/client.d.ts +43 -3
  3. package/dist/client.js +255 -85
  4. package/dist/config.d.ts +7 -1
  5. package/dist/config.js +114 -6
  6. package/dist/index.js +14 -6
  7. package/dist/instructions.d.ts +6 -0
  8. package/dist/instructions.js +24 -0
  9. package/dist/schemas/cameras.d.ts +32 -30
  10. package/dist/schemas/cameras.js +7 -6
  11. package/dist/schemas/common.d.ts +31 -6
  12. package/dist/schemas/common.js +45 -6
  13. package/dist/schemas/devices.d.ts +2 -10
  14. package/dist/schemas/devices.js +35 -26
  15. package/dist/schemas/misc.d.ts +50 -18
  16. package/dist/schemas/misc.js +23 -15
  17. package/dist/schemas/pos.d.ts +1 -1
  18. package/dist/schemas/pos.js +1 -1
  19. package/dist/tls.d.ts +41 -0
  20. package/dist/tls.js +75 -0
  21. package/dist/tools/alarm-manager.d.ts +3 -0
  22. package/dist/tools/alarm-manager.js +32 -0
  23. package/dist/tools/arm-profiles.d.ts +8 -2
  24. package/dist/tools/arm-profiles.js +98 -95
  25. package/dist/tools/cameras.d.ts +2 -2
  26. package/dist/tools/cameras.js +156 -181
  27. package/dist/tools/device-actions.d.ts +2 -2
  28. package/dist/tools/device-actions.js +84 -97
  29. package/dist/tools/devices.d.ts +6 -3
  30. package/dist/tools/devices.js +50 -51
  31. package/dist/tools/files.d.ts +2 -2
  32. package/dist/tools/files.js +82 -57
  33. package/dist/tools/index.d.ts +2 -2
  34. package/dist/tools/index.js +15 -10
  35. package/dist/tools/liveviews.d.ts +2 -2
  36. package/dist/tools/liveviews.js +52 -52
  37. package/dist/tools/pos.d.ts +6 -4
  38. package/dist/tools/pos.js +15 -20
  39. package/dist/tools/registrar.d.ts +34 -0
  40. package/dist/tools/registrar.js +103 -0
  41. package/dist/tools/subscriptions.d.ts +13 -2
  42. package/dist/tools/subscriptions.js +148 -27
  43. package/dist/tools/system.d.ts +2 -2
  44. package/dist/tools/system.js +5 -15
  45. package/dist/tools/users.d.ts +2 -2
  46. package/dist/tools/users.js +18 -35
  47. package/dist/utils/limits.d.ts +33 -0
  48. package/dist/utils/limits.js +74 -0
  49. package/dist/utils/paging.d.ts +15 -0
  50. package/dist/utils/paging.js +28 -0
  51. package/dist/utils/params.d.ts +13 -0
  52. package/dist/utils/params.js +40 -0
  53. package/dist/utils/responses.js +2 -3
  54. package/dist/utils/safety.d.ts +39 -3
  55. package/dist/utils/safety.js +45 -3
  56. package/dist/utils/url.d.ts +4 -1
  57. package/dist/utils/url.js +4 -1
  58. package/dist/version.d.ts +6 -0
  59. package/dist/version.js +11 -0
  60. package/package.json +14 -12
@@ -1,11 +1,11 @@
1
1
  import { z } from "zod";
2
2
  /**
3
3
  * Schemas below are verified against live UniFi Protect Integration API 7.3.47
4
- * responses (Melrose NVR Pro console, 2026-09-14) where instances exist, and
5
- * against the 7.3.47 published docs otherwise (arm profiles, files — no
6
- * instances on the console). The Integration API surface is much thinner than
4
+ * responses (Melrose NVR Pro console, 2026-09-14) where instances exist —
5
+ * re-verified live on 7.3.70 (2026-10-12) — and against the 7.3.70 published
6
+ * docs otherwise (arm profiles, files — no instances on the console). The Integration API surface is much thinner than
7
7
  * the internal Protect app API — only observed/documented fields are typed;
8
- * `.passthrough()` still allows anything extra a firmware/hardware mix adds.
8
+ * `.loose()` still allows anything extra a firmware/hardware mix adds.
9
9
  */
10
10
  export declare const infoOutputSchema: {
11
11
  applicationVersion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -17,13 +17,13 @@ export declare const nvrSchema: z.ZodObject<{
17
17
  modelKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
18
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
19
19
  mac: z.ZodOptional<z.ZodNullable<z.ZodString>>;
20
- doorbellSettings: z.ZodOptional<z.ZodObject<{
20
+ doorbellSettings: z.ZodOptional<z.ZodNullable<z.ZodObject<{
21
21
  defaultMessageText: z.ZodOptional<z.ZodNullable<z.ZodString>>;
22
22
  defaultMessageResetTimeoutMs: z.ZodOptional<z.ZodUnknown>;
23
23
  customMessages: z.ZodOptional<z.ZodUnknown>;
24
24
  customImages: z.ZodOptional<z.ZodUnknown>;
25
- }, z.core.$loose>>;
26
- armMode: z.ZodOptional<z.ZodObject<{
25
+ }, z.core.$loose>>>;
26
+ armMode: z.ZodOptional<z.ZodNullable<z.ZodObject<{
27
27
  status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
28
28
  armProfileId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
29
  armedAt: z.ZodOptional<z.ZodUnknown>;
@@ -32,7 +32,7 @@ export declare const nvrSchema: z.ZodObject<{
32
32
  breachEventCount: z.ZodOptional<z.ZodUnknown>;
33
33
  breachTriggerEventId: z.ZodOptional<z.ZodUnknown>;
34
34
  breachEventId: z.ZodOptional<z.ZodUnknown>;
35
- }, z.core.$loose>>;
35
+ }, z.core.$loose>>>;
36
36
  }, z.core.$loose>;
37
37
  export declare const nvrOutputSchema: {
38
38
  type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -41,13 +41,13 @@ export declare const nvrOutputSchema: {
41
41
  modelKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
42
42
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
43
43
  mac: z.ZodOptional<z.ZodNullable<z.ZodString>>;
44
- doorbellSettings: z.ZodOptional<z.ZodObject<{
44
+ doorbellSettings: z.ZodOptional<z.ZodNullable<z.ZodObject<{
45
45
  defaultMessageText: z.ZodOptional<z.ZodNullable<z.ZodString>>;
46
46
  defaultMessageResetTimeoutMs: z.ZodOptional<z.ZodUnknown>;
47
47
  customMessages: z.ZodOptional<z.ZodUnknown>;
48
48
  customImages: z.ZodOptional<z.ZodUnknown>;
49
- }, z.core.$loose>>;
50
- armMode: z.ZodOptional<z.ZodObject<{
49
+ }, z.core.$loose>>>;
50
+ armMode: z.ZodOptional<z.ZodNullable<z.ZodObject<{
51
51
  status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
52
52
  armProfileId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
53
53
  armedAt: z.ZodOptional<z.ZodUnknown>;
@@ -56,7 +56,7 @@ export declare const nvrOutputSchema: {
56
56
  breachEventCount: z.ZodOptional<z.ZodUnknown>;
57
57
  breachTriggerEventId: z.ZodOptional<z.ZodUnknown>;
58
58
  breachEventId: z.ZodOptional<z.ZodUnknown>;
59
- }, z.core.$loose>>;
59
+ }, z.core.$loose>>>;
60
60
  };
61
61
  export declare const userSchema: z.ZodObject<{
62
62
  id: z.ZodOptional<z.ZodString>;
@@ -86,6 +86,8 @@ export declare const userListOutputSchema: {
86
86
  email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
87
  ucoreUserId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
88
88
  }, z.core.$loose>>;
89
+ totalCount: z.ZodOptional<z.ZodNumber>;
90
+ offset: z.ZodOptional<z.ZodNumber>;
89
91
  };
90
92
  export declare const ulpUserSchema: z.ZodObject<{
91
93
  id: z.ZodOptional<z.ZodString>;
@@ -115,6 +117,8 @@ export declare const ulpUserListOutputSchema: {
115
117
  email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
116
118
  status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
117
119
  }, z.core.$loose>>;
120
+ totalCount: z.ZodOptional<z.ZodNumber>;
121
+ offset: z.ZodOptional<z.ZodNumber>;
118
122
  };
119
123
  export declare const liveviewSchema: z.ZodObject<{
120
124
  id: z.ZodOptional<z.ZodString>;
@@ -124,11 +128,11 @@ export declare const liveviewSchema: z.ZodObject<{
124
128
  isGlobal: z.ZodOptional<z.ZodUnknown>;
125
129
  layout: z.ZodOptional<z.ZodUnknown>;
126
130
  owner: z.ZodOptional<z.ZodNullable<z.ZodString>>;
127
- slots: z.ZodOptional<z.ZodArray<z.ZodObject<{
131
+ slots: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
128
132
  cameras: z.ZodOptional<z.ZodUnknown>;
129
133
  cycleMode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
134
  cycleInterval: z.ZodOptional<z.ZodUnknown>;
131
- }, z.core.$loose>>>;
135
+ }, z.core.$loose>>>>;
132
136
  }, z.core.$loose>;
133
137
  export declare const liveviewOutputSchema: {
134
138
  id: z.ZodOptional<z.ZodString>;
@@ -138,11 +142,11 @@ export declare const liveviewOutputSchema: {
138
142
  isGlobal: z.ZodOptional<z.ZodUnknown>;
139
143
  layout: z.ZodOptional<z.ZodUnknown>;
140
144
  owner: z.ZodOptional<z.ZodNullable<z.ZodString>>;
141
- slots: z.ZodOptional<z.ZodArray<z.ZodObject<{
145
+ slots: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
142
146
  cameras: z.ZodOptional<z.ZodUnknown>;
143
147
  cycleMode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
144
148
  cycleInterval: z.ZodOptional<z.ZodUnknown>;
145
- }, z.core.$loose>>>;
149
+ }, z.core.$loose>>>>;
146
150
  };
147
151
  export declare const liveviewListOutputSchema: {
148
152
  result: z.ZodArray<z.ZodObject<{
@@ -153,12 +157,14 @@ export declare const liveviewListOutputSchema: {
153
157
  isGlobal: z.ZodOptional<z.ZodUnknown>;
154
158
  layout: z.ZodOptional<z.ZodUnknown>;
155
159
  owner: z.ZodOptional<z.ZodNullable<z.ZodString>>;
156
- slots: z.ZodOptional<z.ZodArray<z.ZodObject<{
160
+ slots: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
157
161
  cameras: z.ZodOptional<z.ZodUnknown>;
158
162
  cycleMode: z.ZodOptional<z.ZodNullable<z.ZodString>>;
159
163
  cycleInterval: z.ZodOptional<z.ZodUnknown>;
160
- }, z.core.$loose>>>;
164
+ }, z.core.$loose>>>>;
161
165
  }, z.core.$loose>>;
166
+ totalCount: z.ZodOptional<z.ZodNumber>;
167
+ offset: z.ZodOptional<z.ZodNumber>;
162
168
  };
163
169
  export declare const armProfileSchema: z.ZodObject<{
164
170
  id: z.ZodOptional<z.ZodString>;
@@ -183,6 +189,8 @@ export declare const armProfileListOutputSchema: {
183
189
  createdAt: z.ZodOptional<z.ZodUnknown>;
184
190
  updatedAt: z.ZodOptional<z.ZodUnknown>;
185
191
  }, z.core.$loose>>;
192
+ totalCount: z.ZodOptional<z.ZodNumber>;
193
+ offset: z.ZodOptional<z.ZodNumber>;
186
194
  };
187
195
  export declare const armProfileOutputSchema: {
188
196
  id: z.ZodOptional<z.ZodString>;
@@ -196,21 +204,45 @@ export declare const armProfileOutputSchema: {
196
204
  updatedAt: z.ZodOptional<z.ZodUnknown>;
197
205
  };
198
206
  export declare const fileSchema: z.ZodObject<{
207
+ id: z.ZodOptional<z.ZodString>;
199
208
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
200
209
  type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
201
210
  originalName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
202
211
  path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
212
+ size: z.ZodOptional<z.ZodUnknown>;
213
+ createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
214
+ updatedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
215
+ metadata: z.ZodOptional<z.ZodUnknown>;
203
216
  }, z.core.$loose>;
204
217
  export declare const fileListOutputSchema: {
205
218
  result: z.ZodArray<z.ZodObject<{
219
+ id: z.ZodOptional<z.ZodString>;
206
220
  name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
207
221
  type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
208
222
  originalName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
209
223
  path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
224
+ size: z.ZodOptional<z.ZodUnknown>;
225
+ createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
226
+ updatedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
227
+ metadata: z.ZodOptional<z.ZodUnknown>;
210
228
  }, z.core.$loose>>;
229
+ totalCount: z.ZodOptional<z.ZodNumber>;
230
+ offset: z.ZodOptional<z.ZodNumber>;
231
+ };
232
+ export declare const fileOutputSchema: {
233
+ id: z.ZodOptional<z.ZodString>;
234
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
235
+ type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
236
+ originalName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
237
+ path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
238
+ size: z.ZodOptional<z.ZodUnknown>;
239
+ createdAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
240
+ updatedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
241
+ metadata: z.ZodOptional<z.ZodUnknown>;
211
242
  };
212
243
  export declare const subscriptionOutputSchema: {
213
244
  messages: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
214
245
  duration: z.ZodOptional<z.ZodUnknown>;
215
246
  error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
247
+ truncated: z.ZodOptional<z.ZodUnknown>;
216
248
  };
@@ -1,15 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.subscriptionOutputSchema = exports.fileListOutputSchema = exports.fileSchema = exports.armProfileOutputSchema = exports.armProfileListOutputSchema = exports.armProfileSchema = exports.liveviewListOutputSchema = exports.liveviewOutputSchema = exports.liveviewSchema = exports.ulpUserListOutputSchema = exports.ulpUserOutputSchema = exports.ulpUserSchema = exports.userListOutputSchema = exports.userOutputSchema = exports.userSchema = exports.nvrOutputSchema = exports.nvrSchema = exports.infoOutputSchema = void 0;
3
+ exports.subscriptionOutputSchema = exports.fileOutputSchema = exports.fileListOutputSchema = exports.fileSchema = exports.armProfileOutputSchema = exports.armProfileListOutputSchema = exports.armProfileSchema = exports.liveviewListOutputSchema = exports.liveviewOutputSchema = exports.liveviewSchema = exports.ulpUserListOutputSchema = exports.ulpUserOutputSchema = exports.ulpUserSchema = exports.userListOutputSchema = exports.userOutputSchema = exports.userSchema = exports.nvrOutputSchema = exports.nvrSchema = exports.infoOutputSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const common_js_1 = require("./common.js");
6
6
  /**
7
7
  * Schemas below are verified against live UniFi Protect Integration API 7.3.47
8
- * responses (Melrose NVR Pro console, 2026-09-14) where instances exist, and
9
- * against the 7.3.47 published docs otherwise (arm profiles, files — no
10
- * instances on the console). The Integration API surface is much thinner than
8
+ * responses (Melrose NVR Pro console, 2026-09-14) where instances exist —
9
+ * re-verified live on 7.3.70 (2026-10-12) — and against the 7.3.70 published
10
+ * docs otherwise (arm profiles, files — no instances on the console). The Integration API surface is much thinner than
11
11
  * the internal Protect app API — only observed/documented fields are typed;
12
- * `.passthrough()` still allows anything extra a firmware/hardware mix adds.
12
+ * `.loose()` still allows anything extra a firmware/hardware mix adds.
13
13
  */
14
14
  // --- System ---
15
15
  exports.infoOutputSchema = {
@@ -30,7 +30,7 @@ const doorbellSettingsSchema = (0, common_js_1.passthroughObject)({
30
30
  defaultMessageText: (0, common_js_1.nullableString)("Default doorbell LCD message"),
31
31
  defaultMessageResetTimeoutMs: (0, common_js_1.unknownField)("Reset timeout in ms (number)"),
32
32
  customMessages: (0, common_js_1.unknownField)("Configured custom LCD messages (array of strings)"),
33
- customImages: (0, common_js_1.unknownField)("Configured custom LCD images (array)"),
33
+ customImages: (0, common_js_1.unknownField)("Configured custom LCD images (array of objects: preview, sprite)"),
34
34
  });
35
35
  exports.nvrSchema = (0, common_js_1.passthroughObject)({
36
36
  id: (0, common_js_1.idField)("NVR ID"),
@@ -38,8 +38,8 @@ exports.nvrSchema = (0, common_js_1.passthroughObject)({
38
38
  name: (0, common_js_1.nullableString)("NVR name"),
39
39
  ...(0, common_js_1.hardwareModelFields)(),
40
40
  mac: (0, common_js_1.nullableString)("MAC address, added in 7.3.x"),
41
- doorbellSettings: doorbellSettingsSchema.optional(),
42
- armMode: armModeSchema.optional(),
41
+ doorbellSettings: doorbellSettingsSchema.nullable().optional(),
42
+ armMode: armModeSchema.nullable().optional(),
43
43
  });
44
44
  // Single object — use the shape directly, NOT a list wrapper.
45
45
  exports.nvrOutputSchema = { ...exports.nvrSchema.shape };
@@ -62,7 +62,7 @@ exports.ulpUserSchema = (0, common_js_1.passthroughObject)({
62
62
  lastName: (0, common_js_1.nullableString)("Last name"),
63
63
  fullName: (0, common_js_1.nullableString)("Full name"),
64
64
  email: (0, common_js_1.nullableString)("Email address, added in 7.3.x. Empty string for service accounts " +
65
- "(verified live 7.3.47)"),
65
+ "(verified live 7.3.70)"),
66
66
  status: (0, common_js_1.nullableString)("Account status, e.g. ACTIVE"),
67
67
  });
68
68
  exports.ulpUserOutputSchema = { ...exports.ulpUserSchema.shape };
@@ -81,12 +81,12 @@ exports.liveviewSchema = (0, common_js_1.passthroughObject)({
81
81
  isGlobal: (0, common_js_1.unknownField)("Whether shared across all users (boolean)"),
82
82
  layout: (0, common_js_1.unknownField)("Grid layout / slot count (number)"),
83
83
  owner: (0, common_js_1.nullableString)("Owning user ID"),
84
- slots: zod_1.z.array(liveviewSlotSchema).optional().describe("Camera slots"),
84
+ slots: zod_1.z.array(liveviewSlotSchema).nullable().optional().describe("Camera slots"),
85
85
  });
86
86
  exports.liveviewOutputSchema = { ...exports.liveviewSchema.shape };
87
87
  exports.liveviewListOutputSchema = (0, common_js_1.listResultSchema)(exports.liveviewSchema);
88
88
  // --- Arm profiles ---
89
- // Fields from the 7.1.83 docs (no instances on the console to verify live).
89
+ // Fields from the 7.3.70 docs (no instances on the console to verify live).
90
90
  // Arm profiles carry no `modelKey`. Nested arrays stay unknownField.
91
91
  exports.armProfileSchema = (0, common_js_1.passthroughObject)({
92
92
  id: (0, common_js_1.idField)("Arm profile ID"),
@@ -102,21 +102,29 @@ exports.armProfileSchema = (0, common_js_1.passthroughObject)({
102
102
  exports.armProfileListOutputSchema = (0, common_js_1.listResultSchema)(exports.armProfileSchema);
103
103
  exports.armProfileOutputSchema = { ...exports.armProfileSchema.shape };
104
104
  // --- Files ---
105
- // Device asset files (only fileType=animations supported). Shape from 7.1.83
106
- // docs: { name, type, originalName, path } — no id/modelKey.
105
+ // Device asset files (only fileType=animations supported). The 7.3.70 docs show
106
+ // { name, type, originalName, path }; live 7.3.70 records (2026-10-12) also
107
+ // carry id (equal to name), size, createdAt, updatedAt and metadata. No modelKey.
107
108
  exports.fileSchema = (0, common_js_1.passthroughObject)({
109
+ id: (0, common_js_1.idField)("File ID (same value as name)"),
108
110
  name: (0, common_js_1.nullableString)("Stored file name (server-generated)"),
109
111
  type: (0, common_js_1.nullableString)('Asset file type, e.g. "animations"'),
110
- originalName: (0, common_js_1.nullableString)("Original uploaded file name"),
112
+ originalName: (0, common_js_1.nullableString)('Uploaded file name; Protect appends the stored extension, e.g. "welcome.gif.png"'),
111
113
  path: (0, common_js_1.nullableString)("Server-side storage path"),
114
+ size: (0, common_js_1.unknownField)("File size in bytes (number)"),
115
+ createdAt: (0, common_js_1.nullableString)("Creation time (ISO 8601)"),
116
+ updatedAt: (0, common_js_1.nullableString)("Last update time (ISO 8601)"),
117
+ metadata: (0, common_js_1.unknownField)("Asset metadata (object, empty in observed records)"),
112
118
  });
113
119
  exports.fileListOutputSchema = (0, common_js_1.listResultSchema)(exports.fileSchema);
120
+ exports.fileOutputSchema = { ...exports.fileSchema.shape };
114
121
  // --- Subscriptions ---
115
122
  exports.subscriptionOutputSchema = {
116
123
  messages: zod_1.z
117
124
  .array(zod_1.z.unknown())
118
125
  .optional()
119
126
  .describe("Captured WebSocket messages (add/update/remove or event envelopes)"),
120
- duration: (0, common_js_1.unknownField)("Actual listen duration in seconds (number)"),
127
+ duration: (0, common_js_1.unknownField)("Seconds actually spent listening, to 0.1s (number); shorter than requested when the message cap or an error ends the listen early"),
121
128
  error: (0, common_js_1.nullableString)("Connection error, if any"),
129
+ truncated: (0, common_js_1.unknownField)("true when listening stopped early at the message cap (boolean, absent otherwise)"),
122
130
  };
@@ -2,7 +2,7 @@
2
2
  * Point-of-sale event ingestion, added in UniFi Protect 7.3.x
3
3
  * (POST /v1/pos/cameras/{id}/transactions).
4
4
  *
5
- * Shape taken from the 7.3.47 published docs with the response sample expanded
5
+ * Shape taken from the 7.3.70 published docs with the response sample expanded
6
6
  * — there is no POS-capable register on the available console, so this is
7
7
  * doc-verified rather than live-verified. `created` stays `unknownField()`
8
8
  * because we have not observed the boolean live; `eventId` publishes the string
@@ -6,7 +6,7 @@ const common_js_1 = require("./common.js");
6
6
  * Point-of-sale event ingestion, added in UniFi Protect 7.3.x
7
7
  * (POST /v1/pos/cameras/{id}/transactions).
8
8
  *
9
- * Shape taken from the 7.3.47 published docs with the response sample expanded
9
+ * Shape taken from the 7.3.70 published docs with the response sample expanded
10
10
  * — there is no POS-capable register on the available console, so this is
11
11
  * doc-verified rather than live-verified. `created` stays `unknownField()`
12
12
  * because we have not observed the boolean live; `eventId` publishes the string
package/dist/tls.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { X509Certificate } from "node:crypto";
2
+ import { type PeerCertificate } from "node:tls";
3
+ import { type Dispatcher } from "undici";
4
+ /** Every certificate in a PEM bundle. Throws on a malformed certificate block. */
5
+ export declare function pemCertificates(pem: string): X509Certificate[];
6
+ interface TlsSettings {
7
+ verifySsl: boolean;
8
+ caCert?: string;
9
+ }
10
+ /**
11
+ * TLS options for a connection to the console, shared by requests and the
12
+ * subscription WebSocket. They are scoped to this client, not set in the
13
+ * process-wide NODE_TLS_REJECT_UNAUTHORIZED, which turned verification off for
14
+ * every other TLS connection in the process as well.
15
+ */
16
+ export declare function consoleTlsOptions({ verifySsl, caCert }: TlsSettings): {
17
+ rejectUnauthorized: boolean;
18
+ ca?: undefined;
19
+ allowPartialTrustChain?: undefined;
20
+ checkServerIdentity?: undefined;
21
+ } | {
22
+ rejectUnauthorized?: undefined;
23
+ ca?: undefined;
24
+ allowPartialTrustChain?: undefined;
25
+ checkServerIdentity?: undefined;
26
+ } | {
27
+ rejectUnauthorized?: undefined;
28
+ ca: string;
29
+ allowPartialTrustChain: boolean;
30
+ checkServerIdentity: (host: string, cert: PeerCertificate) => Error | undefined;
31
+ };
32
+ /**
33
+ * The dispatcher for requests to the console: its TLS settings, and
34
+ * connections kept open across the gaps between a model's calls instead of
35
+ * paying a new TLS handshake each time. HTTP/1.1 only, as Node's built-in
36
+ * fetch speaks, rather than undici's default of offering HTTP/2. With Node's
37
+ * env proxy turned on, requests go through HTTP_PROXY / HTTPS_PROXY (minus
38
+ * NO_PROXY) as they did through the built-in fetch.
39
+ */
40
+ export declare function consoleDispatcher(tls: TlsSettings): Dispatcher;
41
+ export {};
package/dist/tls.js ADDED
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pemCertificates = pemCertificates;
4
+ exports.consoleTlsOptions = consoleTlsOptions;
5
+ exports.consoleDispatcher = consoleDispatcher;
6
+ const node_crypto_1 = require("node:crypto");
7
+ const node_tls_1 = require("node:tls");
8
+ const undici_1 = require("undici");
9
+ /** Every certificate in a PEM bundle. Throws on a malformed certificate block. */
10
+ function pemCertificates(pem) {
11
+ const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) ?? [];
12
+ return blocks.map((block) => new node_crypto_1.X509Certificate(block));
13
+ }
14
+ /**
15
+ * TLS options for a connection to the console, shared by requests and the
16
+ * subscription WebSocket. They are scoped to this client, not set in the
17
+ * process-wide NODE_TLS_REJECT_UNAUTHORIZED, which turned verification off for
18
+ * every other TLS connection in the process as well.
19
+ */
20
+ function consoleTlsOptions({ verifySsl, caCert }) {
21
+ if (!verifySsl)
22
+ return { rejectUnauthorized: false };
23
+ if (caCert === undefined)
24
+ return {};
25
+ // A console's self-signed certificate is issued to a name like unifi.local
26
+ // but reached by IP, so its hostname never matches. Accept the console when
27
+ // it presents exactly a pinned certificate; any other certificate, such as
28
+ // one issued by a pinned CA, still has to match the hostname.
29
+ const pinned = new Set(pemCertificates(caCert).map((cert) => cert.fingerprint256));
30
+ return {
31
+ ca: caCert,
32
+ // `ca` replaces the public roots, and OpenSSL otherwise accepts a chain
33
+ // only when it ends at a self-signed root. A pinned leaf or intermediate
34
+ // issued by a CA would then never verify, and checkServerIdentity (which
35
+ // runs only after the chain has) would never see the pin.
36
+ allowPartialTrustChain: true,
37
+ checkServerIdentity: (host, cert) => pinned.has(cert.fingerprint256) ? undefined : (0, node_tls_1.checkServerIdentity)(host, cert),
38
+ };
39
+ }
40
+ /**
41
+ * How long an idle connection to the console is kept for the next call, when
42
+ * the server does not say (a Keep-Alive header's timeout wins). It has to end
43
+ * before the server's own idle timeout, or a call reuses a socket the server
44
+ * has just closed and fails with "other side closed"; a write is not retried.
45
+ * Proxies in front of api.ui.com and consoles commonly close at 60 s (AWS
46
+ * load balancers) or 75 s (nginx), so this stays well short of both while
47
+ * still covering the usual gap between a model's calls.
48
+ */
49
+ const KEEP_ALIVE_MS = 20_000;
50
+ /**
51
+ * Whether Node routes its own fetch through HTTP_PROXY / HTTPS_PROXY. Node
52
+ * does so only when asked (NODE_USE_ENV_PROXY=1 or --use-env-proxy), so a
53
+ * proxy variable set for other tools never captures a local console's traffic.
54
+ */
55
+ function nodeUsesEnvProxy() {
56
+ const flag = "--use-env-proxy";
57
+ return (process.env.NODE_USE_ENV_PROXY === "1" ||
58
+ process.execArgv.includes(flag) ||
59
+ (process.env.NODE_OPTIONS ?? "").split(/\s+/).includes(flag));
60
+ }
61
+ /**
62
+ * The dispatcher for requests to the console: its TLS settings, and
63
+ * connections kept open across the gaps between a model's calls instead of
64
+ * paying a new TLS handshake each time. HTTP/1.1 only, as Node's built-in
65
+ * fetch speaks, rather than undici's default of offering HTTP/2. With Node's
66
+ * env proxy turned on, requests go through HTTP_PROXY / HTTPS_PROXY (minus
67
+ * NO_PROXY) as they did through the built-in fetch.
68
+ */
69
+ function consoleDispatcher(tls) {
70
+ const connect = consoleTlsOptions(tls);
71
+ const options = { keepAliveTimeout: KEEP_ALIVE_MS, allowH2: false, connect };
72
+ return nodeUsesEnvProxy()
73
+ ? new undici_1.EnvHttpProxyAgent({ ...options, requestTls: { ...connect, allowH2: false } })
74
+ : new undici_1.Agent(options);
75
+ }
@@ -0,0 +1,3 @@
1
+ import type { ToolRegistrar } from "./registrar.js";
2
+ import { ProtectClient } from "../client.js";
3
+ export declare function registerAlarmManagerTools(server: ToolRegistrar, client: ProtectClient, readOnly: boolean): void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAlarmManagerTools = registerAlarmManagerTools;
4
+ const responses_js_1 = require("../utils/responses.js");
5
+ const safety_js_1 = require("../utils/safety.js");
6
+ const url_js_1 = require("../utils/url.js");
7
+ const params_js_1 = require("../utils/params.js");
8
+ // Each gate's confirm input and handler check share one action text.
9
+ const TRIGGER_WEBHOOK = (0, safety_js_1.confirmGate)("trigger the alarm webhook");
10
+ function registerAlarmManagerTools(server, client, readOnly) {
11
+ if (readOnly)
12
+ return;
13
+ server.registerTool("protect_trigger_alarm_webhook", {
14
+ description: "Trigger an alarm manager webhook by ID. This fires the external alarm action configured for it. Requires confirm: true.",
15
+ inputSchema: {
16
+ id: (0, params_js_1.webhookId)()
17
+ .describe("User-defined webhook ID matching an alarm manager configuration"),
18
+ confirm: TRIGGER_WEBHOOK.input,
19
+ dryRun: (0, safety_js_1.dryRunInput)(),
20
+ },
21
+ annotations: safety_js_1.DESTRUCTIVE,
22
+ }, async ({ id, confirm, dryRun }) => {
23
+ if (dryRun) {
24
+ return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/alarm-manager/webhook/${id}`);
25
+ }
26
+ const denied = TRIGGER_WEBHOOK.check(confirm);
27
+ if (denied)
28
+ return denied;
29
+ await client.post((0, url_js_1.safePath) `/alarm-manager/webhook/${id}`);
30
+ return (0, responses_js_1.formatSuccess)({ triggered: true, webhookId: id });
31
+ });
32
+ }
@@ -1,3 +1,9 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1
+ import type { ToolRegistrar } from "./registrar.js";
2
2
  import { ProtectClient } from "../client.js";
3
- export declare function registerArmProfileTools(server: McpServer, client: ProtectClient, readOnly: boolean): void;
3
+ /**
4
+ * Arm profile settings that change what the alarm protects, and when: a
5
+ * longer activationDelay leaves the site unprotected for longer after arming,
6
+ * and recordEverything decides whether an armed site records at all.
7
+ */
8
+ export declare const ARM_PROFILE_CONFIRM_FIELDS: readonly ["schedules", "automations", "activationDelay", "recordEverything"];
9
+ export declare function registerArmProfileTools(server: ToolRegistrar, client: ProtectClient, readOnly: boolean): void;