@owine/unifi-network-mcp 2.14.2 → 2.15.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.
@@ -3,7 +3,7 @@
3
3
  * field optional, every nested object uses .passthrough() to allow
4
4
  * firmware/hardware-specific fields to flow through unchanged).
5
5
  *
6
- * Verified against UniFi Network API 10.6.106. Where the docs collapse
6
+ * Verified against UniFi Network API 11.0.83. Where the docs collapse
7
7
  * nested arrays/objects (e.g. interfaces.ports[], radios[]), the schema
8
8
  * uses passthrough records so the contract doesn't lock to fields we
9
9
  * haven't verified.
@@ -40,6 +40,7 @@ export declare const networkOutputSchema: {
40
40
  ipv6Configuration: z.ZodOptional<z.ZodUnknown>;
41
41
  metadata: z.ZodOptional<z.ZodObject<{
42
42
  origin: z.ZodOptional<z.ZodString>;
43
+ configurable: z.ZodOptional<z.ZodBoolean>;
43
44
  }, z.core.$loose>>;
44
45
  };
45
46
  export declare const listNetworksOutputSchema: {
@@ -64,6 +65,7 @@ export declare const listNetworksOutputSchema: {
64
65
  ipv6Configuration: z.ZodOptional<z.ZodUnknown>;
65
66
  metadata: z.ZodOptional<z.ZodObject<{
66
67
  origin: z.ZodOptional<z.ZodString>;
68
+ configurable: z.ZodOptional<z.ZodBoolean>;
67
69
  }, z.core.$loose>>;
68
70
  }, z.core.$loose>>;
69
71
  };
@@ -129,6 +131,7 @@ export declare const firewallZoneOutputSchema: {
129
131
  networkIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
130
132
  metadata: z.ZodOptional<z.ZodObject<{
131
133
  origin: z.ZodOptional<z.ZodString>;
134
+ configurable: z.ZodOptional<z.ZodBoolean>;
132
135
  }, z.core.$loose>>;
133
136
  };
134
137
  export declare const listFirewallZonesOutputSchema: {
@@ -142,6 +145,7 @@ export declare const listFirewallZonesOutputSchema: {
142
145
  networkIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
143
146
  metadata: z.ZodOptional<z.ZodObject<{
144
147
  origin: z.ZodOptional<z.ZodString>;
148
+ configurable: z.ZodOptional<z.ZodBoolean>;
145
149
  }, z.core.$loose>>;
146
150
  }, z.core.$loose>>;
147
151
  };
@@ -157,10 +161,12 @@ export declare const firewallPolicyOutputSchema: {
157
161
  ipsecFilter: z.ZodOptional<z.ZodUnknown>;
158
162
  schedule: z.ZodOptional<z.ZodUnknown>;
159
163
  loggingEnabled: z.ZodOptional<z.ZodBoolean>;
164
+ reportFlowEnabled: z.ZodOptional<z.ZodBoolean>;
160
165
  index: z.ZodOptional<z.ZodNumber>;
161
166
  description: z.ZodOptional<z.ZodString>;
162
167
  metadata: z.ZodOptional<z.ZodObject<{
163
168
  origin: z.ZodOptional<z.ZodString>;
169
+ configurable: z.ZodOptional<z.ZodBoolean>;
164
170
  }, z.core.$loose>>;
165
171
  };
166
172
  export declare const listFirewallPoliciesOutputSchema: {
@@ -180,10 +186,12 @@ export declare const listFirewallPoliciesOutputSchema: {
180
186
  ipsecFilter: z.ZodOptional<z.ZodUnknown>;
181
187
  schedule: z.ZodOptional<z.ZodUnknown>;
182
188
  loggingEnabled: z.ZodOptional<z.ZodBoolean>;
189
+ reportFlowEnabled: z.ZodOptional<z.ZodBoolean>;
183
190
  index: z.ZodOptional<z.ZodNumber>;
184
191
  description: z.ZodOptional<z.ZodString>;
185
192
  metadata: z.ZodOptional<z.ZodObject<{
186
193
  origin: z.ZodOptional<z.ZodString>;
194
+ configurable: z.ZodOptional<z.ZodBoolean>;
187
195
  }, z.core.$loose>>;
188
196
  }, z.core.$loose>>;
189
197
  };
@@ -199,14 +207,15 @@ export declare const aclRuleOutputSchema: {
199
207
  name: z.ZodOptional<z.ZodString>;
200
208
  enabled: z.ZodOptional<z.ZodBoolean>;
201
209
  action: z.ZodOptional<z.ZodString>;
202
- description: z.ZodOptional<z.ZodString>;
203
- protocolFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
210
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
211
+ protocolFilter: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
204
212
  sourceFilter: z.ZodOptional<z.ZodUnknown>;
205
213
  destinationFilter: z.ZodOptional<z.ZodUnknown>;
206
214
  enforcingDeviceFilter: z.ZodOptional<z.ZodUnknown>;
207
215
  index: z.ZodOptional<z.ZodNumber>;
208
216
  metadata: z.ZodOptional<z.ZodObject<{
209
217
  origin: z.ZodOptional<z.ZodString>;
218
+ configurable: z.ZodOptional<z.ZodBoolean>;
210
219
  }, z.core.$loose>>;
211
220
  };
212
221
  export declare const listAclRulesOutputSchema: {
@@ -220,14 +229,15 @@ export declare const listAclRulesOutputSchema: {
220
229
  name: z.ZodOptional<z.ZodString>;
221
230
  enabled: z.ZodOptional<z.ZodBoolean>;
222
231
  action: z.ZodOptional<z.ZodString>;
223
- description: z.ZodOptional<z.ZodString>;
224
- protocolFilter: z.ZodOptional<z.ZodArray<z.ZodString>>;
232
+ description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
233
+ protocolFilter: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
225
234
  sourceFilter: z.ZodOptional<z.ZodUnknown>;
226
235
  destinationFilter: z.ZodOptional<z.ZodUnknown>;
227
236
  enforcingDeviceFilter: z.ZodOptional<z.ZodUnknown>;
228
237
  index: z.ZodOptional<z.ZodNumber>;
229
238
  metadata: z.ZodOptional<z.ZodObject<{
230
239
  origin: z.ZodOptional<z.ZodString>;
240
+ configurable: z.ZodOptional<z.ZodBoolean>;
231
241
  }, z.core.$loose>>;
232
242
  }, z.core.$loose>>;
233
243
  };
@@ -243,6 +253,7 @@ export declare const switchStackOutputSchema: {
243
253
  lags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
244
254
  metadata: z.ZodOptional<z.ZodObject<{
245
255
  origin: z.ZodOptional<z.ZodString>;
256
+ configurable: z.ZodOptional<z.ZodBoolean>;
246
257
  }, z.core.$loose>>;
247
258
  };
248
259
  export declare const listSwitchStacksOutputSchema: {
@@ -259,6 +270,7 @@ export declare const listSwitchStacksOutputSchema: {
259
270
  lags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
260
271
  metadata: z.ZodOptional<z.ZodObject<{
261
272
  origin: z.ZodOptional<z.ZodString>;
273
+ configurable: z.ZodOptional<z.ZodBoolean>;
262
274
  }, z.core.$loose>>;
263
275
  }, z.core.$loose>>;
264
276
  };
@@ -269,6 +281,7 @@ export declare const mcLagDomainOutputSchema: {
269
281
  lags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
270
282
  metadata: z.ZodOptional<z.ZodObject<{
271
283
  origin: z.ZodOptional<z.ZodString>;
284
+ configurable: z.ZodOptional<z.ZodBoolean>;
272
285
  }, z.core.$loose>>;
273
286
  };
274
287
  export declare const listMcLagDomainsOutputSchema: {
@@ -283,6 +296,7 @@ export declare const listMcLagDomainsOutputSchema: {
283
296
  lags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
284
297
  metadata: z.ZodOptional<z.ZodObject<{
285
298
  origin: z.ZodOptional<z.ZodString>;
299
+ configurable: z.ZodOptional<z.ZodBoolean>;
286
300
  }, z.core.$loose>>;
287
301
  }, z.core.$loose>>;
288
302
  };
@@ -295,6 +309,7 @@ export declare const lagOutputSchema: {
295
309
  }, z.core.$loose>>>;
296
310
  metadata: z.ZodOptional<z.ZodObject<{
297
311
  origin: z.ZodOptional<z.ZodString>;
312
+ configurable: z.ZodOptional<z.ZodBoolean>;
298
313
  }, z.core.$loose>>;
299
314
  };
300
315
  export declare const listLagsOutputSchema: {
@@ -311,6 +326,7 @@ export declare const listLagsOutputSchema: {
311
326
  }, z.core.$loose>>>;
312
327
  metadata: z.ZodOptional<z.ZodObject<{
313
328
  origin: z.ZodOptional<z.ZodString>;
329
+ configurable: z.ZodOptional<z.ZodBoolean>;
314
330
  }, z.core.$loose>>;
315
331
  }, z.core.$loose>>;
316
332
  };
@@ -321,8 +337,20 @@ export declare const dnsPolicyOutputSchema: {
321
337
  domain: z.ZodOptional<z.ZodString>;
322
338
  ipv4Address: z.ZodOptional<z.ZodString>;
323
339
  ttlSeconds: z.ZodOptional<z.ZodNumber>;
340
+ ipv6Address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
341
+ targetDomain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
342
+ mailServerDomain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
343
+ text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
344
+ serverDomain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
345
+ service: z.ZodOptional<z.ZodNullable<z.ZodString>>;
346
+ protocol: z.ZodOptional<z.ZodNullable<z.ZodString>>;
347
+ port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
348
+ priority: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
349
+ weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
350
+ ipAddress: z.ZodOptional<z.ZodNullable<z.ZodString>>;
324
351
  metadata: z.ZodOptional<z.ZodObject<{
325
352
  origin: z.ZodOptional<z.ZodString>;
353
+ configurable: z.ZodOptional<z.ZodBoolean>;
326
354
  }, z.core.$loose>>;
327
355
  };
328
356
  export declare const listDnsPoliciesOutputSchema: {
@@ -337,8 +365,20 @@ export declare const listDnsPoliciesOutputSchema: {
337
365
  domain: z.ZodOptional<z.ZodString>;
338
366
  ipv4Address: z.ZodOptional<z.ZodString>;
339
367
  ttlSeconds: z.ZodOptional<z.ZodNumber>;
368
+ ipv6Address: z.ZodOptional<z.ZodNullable<z.ZodString>>;
369
+ targetDomain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
370
+ mailServerDomain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
371
+ text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
372
+ serverDomain: z.ZodOptional<z.ZodNullable<z.ZodString>>;
373
+ service: z.ZodOptional<z.ZodNullable<z.ZodString>>;
374
+ protocol: z.ZodOptional<z.ZodNullable<z.ZodString>>;
375
+ port: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
376
+ priority: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
377
+ weight: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
378
+ ipAddress: z.ZodOptional<z.ZodNullable<z.ZodString>>;
340
379
  metadata: z.ZodOptional<z.ZodObject<{
341
380
  origin: z.ZodOptional<z.ZodString>;
381
+ configurable: z.ZodOptional<z.ZodBoolean>;
342
382
  }, z.core.$loose>>;
343
383
  }, z.core.$loose>>;
344
384
  };
@@ -349,6 +389,7 @@ export declare const trafficMatchingListOutputSchema: {
349
389
  items: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
350
390
  metadata: z.ZodOptional<z.ZodObject<{
351
391
  origin: z.ZodOptional<z.ZodString>;
392
+ configurable: z.ZodOptional<z.ZodBoolean>;
352
393
  }, z.core.$loose>>;
353
394
  };
354
395
  export declare const listTrafficMatchingListsOutputSchema: {
@@ -363,6 +404,7 @@ export declare const listTrafficMatchingListsOutputSchema: {
363
404
  items: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
364
405
  metadata: z.ZodOptional<z.ZodObject<{
365
406
  origin: z.ZodOptional<z.ZodString>;
407
+ configurable: z.ZodOptional<z.ZodBoolean>;
366
408
  }, z.core.$loose>>;
367
409
  }, z.core.$loose>>;
368
410
  };
@@ -495,6 +537,7 @@ export declare const listWifiOutputSchema: {
495
537
  broadcastingDeviceFilter: z.ZodOptional<z.ZodUnknown>;
496
538
  metadata: z.ZodOptional<z.ZodObject<{
497
539
  origin: z.ZodOptional<z.ZodString>;
540
+ configurable: z.ZodOptional<z.ZodBoolean>;
498
541
  }, z.core.$loose>>;
499
542
  }, z.core.$loose>>;
500
543
  };
@@ -512,6 +555,7 @@ export declare const getWifiOutputSchema: {
512
555
  broadcastingDeviceFilter: z.ZodOptional<z.ZodUnknown>;
513
556
  metadata: z.ZodOptional<z.ZodObject<{
514
557
  origin: z.ZodOptional<z.ZodString>;
558
+ configurable: z.ZodOptional<z.ZodBoolean>;
515
559
  }, z.core.$loose>>;
516
560
  };
517
561
  export declare const listWansOutputSchema: {
@@ -536,6 +580,7 @@ export declare const listVpnServersOutputSchema: {
536
580
  enabled: z.ZodOptional<z.ZodBoolean>;
537
581
  metadata: z.ZodOptional<z.ZodObject<{
538
582
  origin: z.ZodOptional<z.ZodString>;
583
+ configurable: z.ZodOptional<z.ZodBoolean>;
539
584
  }, z.core.$loose>>;
540
585
  }, z.core.$loose>>;
541
586
  };
@@ -559,6 +604,7 @@ export declare const listRadiusProfilesOutputSchema: {
559
604
  name: z.ZodOptional<z.ZodString>;
560
605
  metadata: z.ZodOptional<z.ZodObject<{
561
606
  origin: z.ZodOptional<z.ZodString>;
607
+ configurable: z.ZodOptional<z.ZodBoolean>;
562
608
  }, z.core.$loose>>;
563
609
  }, z.core.$loose>>;
564
610
  };
@@ -6,14 +6,16 @@ exports.listClientSessionsOutputSchema = exports.listClientHistoryOutputSchema =
6
6
  * field optional, every nested object uses .passthrough() to allow
7
7
  * firmware/hardware-specific fields to flow through unchanged).
8
8
  *
9
- * Verified against UniFi Network API 10.6.106. Where the docs collapse
9
+ * Verified against UniFi Network API 11.0.83. Where the docs collapse
10
10
  * nested arrays/objects (e.g. interfaces.ports[], radios[]), the schema
11
11
  * uses passthrough records so the contract doesn't lock to fields we
12
12
  * haven't verified.
13
13
  */
14
14
  const zod_1 = require("zod");
15
+ // configurable is live-verified (11.0.83) on system-defined zones, policies,
16
+ // networks and RADIUS profiles; it is absent on user-defined resources.
15
17
  const Metadata = zod_1.z
16
- .object({ origin: zod_1.z.string().optional() })
18
+ .object({ origin: zod_1.z.string().optional(), configurable: zod_1.z.boolean().optional() })
17
19
  .passthrough();
18
20
  const PaginationFields = {
19
21
  offset: zod_1.z.number().int().optional(),
@@ -160,7 +162,7 @@ const WifiBroadcast = zod_1.z
160
162
  .passthrough();
161
163
  const ApplicationInfo = zod_1.z
162
164
  .object({
163
- // Live-verified (10.6.106): only applicationVersion is returned.
165
+ // Live-verified (11.0.83): only applicationVersion is returned.
164
166
  applicationVersion: zod_1.z.string().optional(),
165
167
  })
166
168
  .passthrough();
@@ -236,6 +238,8 @@ const FirewallPolicy = zod_1.z
236
238
  ipsecFilter: zod_1.z.unknown().optional(),
237
239
  schedule: zod_1.z.unknown().optional(),
238
240
  loggingEnabled: zod_1.z.boolean().optional(),
241
+ // New in 11.0.83; live-verified as a boolean on every policy.
242
+ reportFlowEnabled: zod_1.z.boolean().optional(),
239
243
  index: zod_1.z.number().optional(),
240
244
  description: zod_1.z.string().optional(),
241
245
  metadata: Metadata.optional(),
@@ -259,8 +263,11 @@ const AclRule = zod_1.z
259
263
  name: zod_1.z.string().optional(),
260
264
  enabled: zod_1.z.boolean().optional(),
261
265
  action: zod_1.z.string().optional(),
262
- description: zod_1.z.string().optional(),
263
- protocolFilter: zod_1.z.array(zod_1.z.string()).optional(),
266
+ // The docs give null a meaning ("When null, the rule will be applied to
267
+ // all protocols") and ACL samples already carry explicit nulls, so both
268
+ // must accept null or a protocol-agnostic rule hard-fails with -32602.
269
+ description: zod_1.z.string().nullable().optional(),
270
+ protocolFilter: zod_1.z.array(zod_1.z.string()).nullable().optional(),
264
271
  sourceFilter: zod_1.z.unknown().optional(),
265
272
  destinationFilter: zod_1.z.unknown().optional(),
266
273
  enforcingDeviceFilter: zod_1.z.unknown().optional(),
@@ -322,6 +329,19 @@ const DnsPolicy = zod_1.z
322
329
  domain: zod_1.z.string().optional(),
323
330
  ipv4Address: zod_1.z.string().optional(),
324
331
  ttlSeconds: zod_1.z.number().optional(),
332
+ // Type-specific fields from the 11.0.83 response samples (no live DNS
333
+ // policies to sample), so nullable to be safe.
334
+ ipv6Address: zod_1.z.string().nullable().optional(),
335
+ targetDomain: zod_1.z.string().nullable().optional(),
336
+ mailServerDomain: zod_1.z.string().nullable().optional(),
337
+ text: zod_1.z.string().nullable().optional(),
338
+ serverDomain: zod_1.z.string().nullable().optional(),
339
+ service: zod_1.z.string().nullable().optional(),
340
+ protocol: zod_1.z.string().nullable().optional(),
341
+ port: zod_1.z.number().nullable().optional(),
342
+ priority: zod_1.z.number().nullable().optional(),
343
+ weight: zod_1.z.number().nullable().optional(),
344
+ ipAddress: zod_1.z.string().nullable().optional(),
325
345
  metadata: Metadata.optional(),
326
346
  })
327
347
  .passthrough();
@@ -334,7 +354,7 @@ const TrafficMatchingList = zod_1.z
334
354
  metadata: Metadata.optional(),
335
355
  })
336
356
  .passthrough();
337
- // Verified against the live Integration API (10.6.106). The WAN and
357
+ // Verified against the live Integration API (11.0.83). The WAN and
338
358
  // site-to-site-tunnel list rows are intentionally minimal in the API.
339
359
  const Wan = zod_1.z
340
360
  .object({
@@ -0,0 +1,24 @@
1
+ /**
2
+ * WiFi broadcasts come back from the API with their passphrases in plaintext
3
+ * (`securityConfiguration.passphrase`, and one per VLAN in
4
+ * `securityConfiguration.presharedKeys[].passphrase` on PPSK SSIDs). None of
5
+ * this is in the published docs. Tools redact these values by default so they
6
+ * do not land in LLM transcripts; callers opt in with `includeSecrets: true`.
7
+ */
8
+ export declare const REDACTED = "[REDACTED]";
9
+ /** Deep copy of `data` with every secret-named string value replaced by REDACTED. */
10
+ export declare function redactSecrets<T>(data: T): T;
11
+ /**
12
+ * Replace each REDACTED placeholder in `value` with the real value found at the
13
+ * same path in `current`. This lets a caller echo back a redacted object from a
14
+ * read tool without overwriting the passphrase with the placeholder text.
15
+ * Throws if a placeholder has no real value to restore (e.g. on create, or when
16
+ * a new PPSK entry is added without its passphrase).
17
+ *
18
+ * Array items are matched by index, which is only safe while the item is
19
+ * otherwise unchanged: a reordered `presharedKeys[]` must not hand one VLAN's
20
+ * passphrase to another. So a placeholder inside an array item is restored only
21
+ * when that item equals the current item at the same index apart from secrets.
22
+ */
23
+ export declare function restoreRedacted(value: unknown, current: unknown, path?: string): unknown;
24
+ export declare function containsRedacted(value: unknown): boolean;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ /**
3
+ * WiFi broadcasts come back from the API with their passphrases in plaintext
4
+ * (`securityConfiguration.passphrase`, and one per VLAN in
5
+ * `securityConfiguration.presharedKeys[].passphrase` on PPSK SSIDs). None of
6
+ * this is in the published docs. Tools redact these values by default so they
7
+ * do not land in LLM transcripts; callers opt in with `includeSecrets: true`.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.REDACTED = void 0;
11
+ exports.redactSecrets = redactSecrets;
12
+ exports.restoreRedacted = restoreRedacted;
13
+ exports.containsRedacted = containsRedacted;
14
+ exports.REDACTED = "[REDACTED]";
15
+ const SECRET_KEYS = new Set(["passphrase", "password", "secret", "sharedSecret"]);
16
+ /** Deep copy of `data` with every secret-named string value replaced by REDACTED. */
17
+ function redactSecrets(data) {
18
+ return redact(data);
19
+ }
20
+ function redact(value) {
21
+ if (Array.isArray(value))
22
+ return value.map(redact);
23
+ if (value === null || typeof value !== "object")
24
+ return value;
25
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
26
+ key,
27
+ SECRET_KEYS.has(key) && typeof entry === "string" ? exports.REDACTED : redact(entry),
28
+ ]));
29
+ }
30
+ /**
31
+ * Replace each REDACTED placeholder in `value` with the real value found at the
32
+ * same path in `current`. This lets a caller echo back a redacted object from a
33
+ * read tool without overwriting the passphrase with the placeholder text.
34
+ * Throws if a placeholder has no real value to restore (e.g. on create, or when
35
+ * a new PPSK entry is added without its passphrase).
36
+ *
37
+ * Array items are matched by index, which is only safe while the item is
38
+ * otherwise unchanged: a reordered `presharedKeys[]` must not hand one VLAN's
39
+ * passphrase to another. So a placeholder inside an array item is restored only
40
+ * when that item equals the current item at the same index apart from secrets.
41
+ */
42
+ function restoreRedacted(value, current, path = "body") {
43
+ if (value === exports.REDACTED) {
44
+ if (typeof current === "string")
45
+ return current;
46
+ throw new Error(`${path} is "${exports.REDACTED}" with no existing value to keep — pass the real secret, or omit the field`);
47
+ }
48
+ if (Array.isArray(value)) {
49
+ const currentItems = Array.isArray(current) ? current : [];
50
+ return value.map((item, i) => {
51
+ const itemPath = `${path}[${i}]`;
52
+ if (!containsRedacted(item))
53
+ return item;
54
+ if (stableStringify(redact(item)) !== stableStringify(redact(currentItems[i]))) {
55
+ throw new Error(`${itemPath} contains "${exports.REDACTED}" but differs from the existing entry at that position — pass the real secret for changed or reordered entries`);
56
+ }
57
+ return restoreRedacted(item, currentItems[i], itemPath);
58
+ });
59
+ }
60
+ if (value === null || typeof value !== "object")
61
+ return value;
62
+ const currentObject = current !== null && typeof current === "object" && !Array.isArray(current)
63
+ ? current
64
+ : {};
65
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
66
+ key,
67
+ restoreRedacted(entry, currentObject[key], `${path}.${key}`),
68
+ ]));
69
+ }
70
+ function containsRedacted(value) {
71
+ if (value === exports.REDACTED)
72
+ return true;
73
+ if (Array.isArray(value))
74
+ return value.some(containsRedacted);
75
+ if (value === null || typeof value !== "object")
76
+ return false;
77
+ return Object.values(value).some(containsRedacted);
78
+ }
79
+ function stableStringify(value) {
80
+ if (Array.isArray(value))
81
+ return `[${value.map(stableStringify).join(",")}]`;
82
+ if (value === null || typeof value !== "object")
83
+ return JSON.stringify(value) ?? "undefined";
84
+ const entries = Object.entries(value)
85
+ .filter(([, entry]) => entry !== undefined)
86
+ .sort(([a], [b]) => a.localeCompare(b))
87
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`);
88
+ return `{${entries.join(",")}}`;
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owine/unifi-network-mcp",
3
- "version": "2.14.2",
3
+ "version": "2.15.0",
4
4
  "description": "MCP server for the UniFi Network API",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "engines": {
23
23
  "node": "^22.13.0 || ^24.0.0"
24
24
  },
25
- "packageManager": "pnpm@12.8.2",
25
+ "packageManager": "pnpm@12.11.2",
26
26
  "repository": {
27
27
  "type": "git",
28
28
  "url": "git+https://github.com/owine/unifi-network-mcp.git"
@@ -37,18 +37,18 @@
37
37
  "author": "owine",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@modelcontextprotocol/sdk": "1.32.0",
40
+ "@modelcontextprotocol/sdk": "1.32.1",
41
41
  "zod": "4.6.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/js": "10.0.1",
45
- "@types/node": "24.19.1",
45
+ "@types/node": "24.19.2",
46
46
  "@typescript/native": "npm:typescript@7.0.2",
47
47
  "@vitest/coverage-v8": "5.0.3",
48
48
  "eslint": "10.12.0",
49
- "lefthook": "2.1.16",
49
+ "lefthook": "2.2.1",
50
50
  "typescript": "npm:@typescript/typescript6@6.0.2",
51
- "typescript-eslint": "8.71.0",
51
+ "typescript-eslint": "8.71.1",
52
52
  "vitest": "5.0.3"
53
53
  }
54
54
  }