@owine/unifi-network-mcp 2.15.0 → 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.
- package/LICENSE +21 -0
- package/README.md +226 -73
- package/dist/client.d.ts +13 -1
- package/dist/client.js +142 -16
- package/dist/config.d.ts +11 -0
- package/dist/config.js +102 -3
- package/dist/guard.d.ts +46 -0
- package/dist/guard.js +171 -0
- package/dist/index.js +15 -4
- package/dist/instructions.d.ts +9 -0
- package/dist/instructions.js +32 -0
- package/dist/tls.d.ts +14 -0
- package/dist/tls.js +58 -0
- package/dist/tools/acl.js +123 -50
- package/dist/tools/client-history.js +20 -10
- package/dist/tools/clients.js +10 -9
- package/dist/tools/devices.js +21 -20
- package/dist/tools/dns-policies.js +27 -27
- package/dist/tools/firewall.d.ts +2 -1
- package/dist/tools/firewall.js +200 -75
- package/dist/tools/hotspot.js +35 -28
- package/dist/tools/index.d.ts +13 -1
- package/dist/tools/index.js +63 -4
- package/dist/tools/networks.d.ts +2 -1
- package/dist/tools/networks.js +44 -31
- package/dist/tools/sites.js +2 -2
- package/dist/tools/supporting.js +22 -21
- package/dist/tools/switching.js +16 -15
- package/dist/tools/system.js +1 -1
- package/dist/tools/traffic-matching.d.ts +2 -1
- package/dist/tools/traffic-matching.js +22 -20
- package/dist/tools/wifi.js +76 -30
- package/dist/utils/full-put.d.ts +11 -2
- package/dist/utils/full-put.js +25 -2
- package/dist/utils/output-schemas.d.ts +26 -4
- package/dist/utils/output-schemas.js +80 -48
- package/dist/utils/params.d.ts +7 -0
- package/dist/utils/params.js +12 -0
- package/dist/utils/responses.d.ts +2 -0
- package/dist/utils/responses.js +28 -2
- package/dist/utils/safety.d.ts +35 -1
- package/dist/utils/safety.js +53 -3
- package/dist/utils/secrets.js +3 -0
- package/dist/utils/wan-exposure.d.ts +69 -0
- package/dist/utils/wan-exposure.js +245 -0
- package/dist/write-log.d.ts +52 -0
- package/dist/write-log.js +178 -0
- package/dist/write-tools.d.ts +27 -0
- package/dist/write-tools.js +50 -0
- package/package.json +17 -13
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.listClientSessionsOutputSchema = exports.listClientHistoryOutputSchema = exports.ClientSessionEnvelope = exports.ClientSession = exports.HistoryClient = exports.listPendingDevicesOutputSchema = exports.listCountriesOutputSchema = exports.listDpiApplicationsOutputSchema = exports.listDpiCategoriesOutputSchema = exports.listDeviceTagsOutputSchema = exports.listRadiusProfilesOutputSchema = exports.listVpnTunnelsOutputSchema = exports.listVpnServersOutputSchema = exports.listWansOutputSchema = exports.getWifiOutputSchema = exports.listWifiOutputSchema = exports.getClientOutputSchema = exports.listClientsOutputSchema = exports.getDeviceStatisticsOutputSchema = exports.getDeviceOutputSchema = exports.listDevicesOutputSchema = exports.listTrafficMatchingListsOutputSchema = exports.trafficMatchingListOutputSchema = exports.listDnsPoliciesOutputSchema = exports.dnsPolicyOutputSchema = exports.listLagsOutputSchema = exports.lagOutputSchema = exports.listMcLagDomainsOutputSchema = exports.mcLagDomainOutputSchema = exports.listSwitchStacksOutputSchema = exports.switchStackOutputSchema = exports.aclRuleOrderingOutputSchema = exports.listAclRulesOutputSchema = exports.aclRuleOutputSchema = exports.firewallPolicyOrderingOutputSchema = exports.listFirewallPoliciesOutputSchema = exports.firewallPolicyOutputSchema = exports.listFirewallZonesOutputSchema = exports.firewallZoneOutputSchema = exports.createVouchersOutputSchema = exports.listVouchersOutputSchema = exports.voucherOutputSchema = exports.getNetworkReferencesOutputSchema = exports.listNetworksOutputSchema = exports.networkOutputSchema = exports.listSitesOutputSchema = exports.getInfoOutputSchema = void 0;
|
|
4
|
+
exports.looseOutput = looseOutput;
|
|
5
|
+
exports.writeResult = writeResult;
|
|
4
6
|
/**
|
|
5
|
-
* Output schemas for
|
|
6
|
-
*
|
|
7
|
+
* Output schemas for every read tool, and for write tools whose response
|
|
8
|
+
* echoes the resource (loose strategy: every non-key
|
|
9
|
+
* field optional, every nested object uses .loose() to allow
|
|
7
10
|
* firmware/hardware-specific fields to flow through unchanged).
|
|
8
11
|
*
|
|
9
12
|
* Verified against UniFi Network API 11.0.83. Where the docs collapse
|
|
10
13
|
* nested arrays/objects (e.g. interfaces.ports[], radios[]), the schema
|
|
11
|
-
* uses
|
|
12
|
-
* haven't verified.
|
|
14
|
+
* uses loose records so the contract doesn't lock to fields we
|
|
15
|
+
* haven't verified. Tools register these raw shapes, and registerAllTools()
|
|
16
|
+
* publishes each as a loose object (src/tools/index.ts), so fields a schema
|
|
17
|
+
* does not list never fail client-side validation.
|
|
13
18
|
*/
|
|
14
19
|
const zod_1 = require("zod");
|
|
15
20
|
// configurable is live-verified (11.0.83) on system-defined zones, policies,
|
|
16
21
|
// networks and RADIUS profiles; it is absent on user-defined resources.
|
|
17
22
|
const Metadata = zod_1.z
|
|
18
23
|
.object({ origin: zod_1.z.string().optional(), configurable: zod_1.z.boolean().optional() })
|
|
19
|
-
.
|
|
24
|
+
.loose();
|
|
20
25
|
const PaginationFields = {
|
|
21
26
|
offset: zod_1.z.number().int().optional(),
|
|
22
27
|
limit: zod_1.z.number().int().optional(),
|
|
@@ -45,7 +50,7 @@ const DeviceListItem = zod_1.z
|
|
|
45
50
|
features: zod_1.z.array(zod_1.z.string()).optional(),
|
|
46
51
|
interfaces: zod_1.z.array(zod_1.z.string()).optional(),
|
|
47
52
|
})
|
|
48
|
-
.
|
|
53
|
+
.loose();
|
|
49
54
|
const DevicePort = zod_1.z
|
|
50
55
|
.object({
|
|
51
56
|
idx: zod_1.z.number(),
|
|
@@ -61,10 +66,10 @@ const DevicePort = zod_1.z
|
|
|
61
66
|
enabled: zod_1.z.boolean().optional(),
|
|
62
67
|
state: zod_1.z.string().optional(),
|
|
63
68
|
})
|
|
64
|
-
.
|
|
69
|
+
.loose()
|
|
65
70
|
.optional(),
|
|
66
71
|
})
|
|
67
|
-
.
|
|
72
|
+
.loose();
|
|
68
73
|
const DeviceRadio = zod_1.z
|
|
69
74
|
.object({
|
|
70
75
|
wlanStandard: zod_1.z.string().optional(),
|
|
@@ -72,7 +77,7 @@ const DeviceRadio = zod_1.z
|
|
|
72
77
|
channelWidthMHz: zod_1.z.number().optional(),
|
|
73
78
|
channel: zod_1.z.number().optional(),
|
|
74
79
|
})
|
|
75
|
-
.
|
|
80
|
+
.loose();
|
|
76
81
|
const DeviceDetail = zod_1.z
|
|
77
82
|
.object({
|
|
78
83
|
...DeviceCommon,
|
|
@@ -81,7 +86,7 @@ const DeviceDetail = zod_1.z
|
|
|
81
86
|
configurationId: zod_1.z.string().optional(),
|
|
82
87
|
uplink: zod_1.z
|
|
83
88
|
.object({ deviceId: zod_1.z.string().optional() })
|
|
84
|
-
.
|
|
89
|
+
.loose()
|
|
85
90
|
.optional(),
|
|
86
91
|
// Detail view: object keyed by capability (switching/accessPoint).
|
|
87
92
|
features: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
@@ -90,10 +95,10 @@ const DeviceDetail = zod_1.z
|
|
|
90
95
|
ports: zod_1.z.array(DevicePort).optional(),
|
|
91
96
|
radios: zod_1.z.array(DeviceRadio).optional(),
|
|
92
97
|
})
|
|
93
|
-
.
|
|
98
|
+
.loose()
|
|
94
99
|
.optional(),
|
|
95
100
|
})
|
|
96
|
-
.
|
|
101
|
+
.loose();
|
|
97
102
|
const DeviceStatistics = zod_1.z
|
|
98
103
|
.object({
|
|
99
104
|
uptimeSec: zod_1.z.number().optional(),
|
|
@@ -109,7 +114,7 @@ const DeviceStatistics = zod_1.z
|
|
|
109
114
|
txRateBps: zod_1.z.number().optional(),
|
|
110
115
|
rxRateBps: zod_1.z.number().optional(),
|
|
111
116
|
})
|
|
112
|
-
.
|
|
117
|
+
.loose()
|
|
113
118
|
.optional(),
|
|
114
119
|
interfaces: zod_1.z
|
|
115
120
|
.object({
|
|
@@ -119,13 +124,13 @@ const DeviceStatistics = zod_1.z
|
|
|
119
124
|
frequencyGHz: zod_1.z.number().optional(),
|
|
120
125
|
txRetriesPct: zod_1.z.number().optional(),
|
|
121
126
|
})
|
|
122
|
-
.
|
|
127
|
+
.loose())
|
|
123
128
|
.optional(),
|
|
124
129
|
})
|
|
125
|
-
.
|
|
130
|
+
.loose()
|
|
126
131
|
.optional(),
|
|
127
132
|
})
|
|
128
|
-
.
|
|
133
|
+
.loose();
|
|
129
134
|
const Client = zod_1.z
|
|
130
135
|
.object({
|
|
131
136
|
id: zod_1.z.string(),
|
|
@@ -137,10 +142,10 @@ const Client = zod_1.z
|
|
|
137
142
|
uplinkDeviceId: zod_1.z.string().optional(),
|
|
138
143
|
access: zod_1.z
|
|
139
144
|
.object({ type: zod_1.z.string().optional() })
|
|
140
|
-
.
|
|
145
|
+
.loose()
|
|
141
146
|
.optional(),
|
|
142
147
|
})
|
|
143
|
-
.
|
|
148
|
+
.loose();
|
|
144
149
|
const WifiBroadcast = zod_1.z
|
|
145
150
|
.object({
|
|
146
151
|
id: zod_1.z.string(),
|
|
@@ -159,20 +164,20 @@ const WifiBroadcast = zod_1.z
|
|
|
159
164
|
broadcastingDeviceFilter: zod_1.z.unknown().optional(),
|
|
160
165
|
metadata: Metadata.optional(),
|
|
161
166
|
})
|
|
162
|
-
.
|
|
167
|
+
.loose();
|
|
163
168
|
const ApplicationInfo = zod_1.z
|
|
164
169
|
.object({
|
|
165
170
|
// Live-verified (11.0.83): only applicationVersion is returned.
|
|
166
171
|
applicationVersion: zod_1.z.string().optional(),
|
|
167
172
|
})
|
|
168
|
-
.
|
|
173
|
+
.loose();
|
|
169
174
|
const Site = zod_1.z
|
|
170
175
|
.object({
|
|
171
176
|
id: zod_1.z.string(),
|
|
172
177
|
internalReference: zod_1.z.string().optional(),
|
|
173
178
|
name: zod_1.z.string().optional(),
|
|
174
179
|
})
|
|
175
|
-
.
|
|
180
|
+
.loose();
|
|
176
181
|
const Network = zod_1.z
|
|
177
182
|
.object({
|
|
178
183
|
id: zod_1.z.string(),
|
|
@@ -192,12 +197,12 @@ const Network = zod_1.z
|
|
|
192
197
|
ipv6Configuration: zod_1.z.unknown().optional(),
|
|
193
198
|
metadata: Metadata.optional(),
|
|
194
199
|
})
|
|
195
|
-
.
|
|
200
|
+
.loose();
|
|
196
201
|
const NetworkReferences = zod_1.z
|
|
197
202
|
.object({
|
|
198
203
|
referenceResources: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
199
204
|
})
|
|
200
|
-
.
|
|
205
|
+
.loose();
|
|
201
206
|
const Voucher = zod_1.z
|
|
202
207
|
.object({
|
|
203
208
|
id: zod_1.z.string(),
|
|
@@ -214,7 +219,7 @@ const Voucher = zod_1.z
|
|
|
214
219
|
authorizedGuestCount: zod_1.z.number().optional(),
|
|
215
220
|
expired: zod_1.z.boolean().optional(),
|
|
216
221
|
})
|
|
217
|
-
.
|
|
222
|
+
.loose();
|
|
218
223
|
const FirewallZone = zod_1.z
|
|
219
224
|
.object({
|
|
220
225
|
id: zod_1.z.string(),
|
|
@@ -222,7 +227,7 @@ const FirewallZone = zod_1.z
|
|
|
222
227
|
networkIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
223
228
|
metadata: Metadata.optional(),
|
|
224
229
|
})
|
|
225
|
-
.
|
|
230
|
+
.loose();
|
|
226
231
|
const FirewallPolicy = zod_1.z
|
|
227
232
|
.object({
|
|
228
233
|
id: zod_1.z.string(),
|
|
@@ -244,7 +249,7 @@ const FirewallPolicy = zod_1.z
|
|
|
244
249
|
description: zod_1.z.string().optional(),
|
|
245
250
|
metadata: Metadata.optional(),
|
|
246
251
|
})
|
|
247
|
-
.
|
|
252
|
+
.loose();
|
|
248
253
|
const FirewallPolicyOrdering = zod_1.z
|
|
249
254
|
.object({
|
|
250
255
|
orderedFirewallPolicyIds: zod_1.z
|
|
@@ -252,10 +257,10 @@ const FirewallPolicyOrdering = zod_1.z
|
|
|
252
257
|
beforeSystemDefined: zod_1.z.array(zod_1.z.string()).optional(),
|
|
253
258
|
afterSystemDefined: zod_1.z.array(zod_1.z.string()).optional(),
|
|
254
259
|
})
|
|
255
|
-
.
|
|
260
|
+
.loose()
|
|
256
261
|
.optional(),
|
|
257
262
|
})
|
|
258
|
-
.
|
|
263
|
+
.loose();
|
|
259
264
|
const AclRule = zod_1.z
|
|
260
265
|
.object({
|
|
261
266
|
id: zod_1.z.string(),
|
|
@@ -276,12 +281,12 @@ const AclRule = zod_1.z
|
|
|
276
281
|
index: zod_1.z.number().int().optional(),
|
|
277
282
|
metadata: Metadata.optional(),
|
|
278
283
|
})
|
|
279
|
-
.
|
|
284
|
+
.loose();
|
|
280
285
|
const AclRuleOrdering = zod_1.z
|
|
281
286
|
.object({
|
|
282
287
|
orderedAclRuleIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
283
288
|
})
|
|
284
|
-
.
|
|
289
|
+
.loose();
|
|
285
290
|
const SwitchStack = zod_1.z
|
|
286
291
|
.object({
|
|
287
292
|
id: zod_1.z.string(),
|
|
@@ -296,7 +301,7 @@ const SwitchStack = zod_1.z
|
|
|
296
301
|
lags: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
297
302
|
metadata: Metadata.optional(),
|
|
298
303
|
})
|
|
299
|
-
.
|
|
304
|
+
.loose();
|
|
300
305
|
const McLagDomain = zod_1.z
|
|
301
306
|
.object({
|
|
302
307
|
id: zod_1.z.string(),
|
|
@@ -305,7 +310,7 @@ const McLagDomain = zod_1.z
|
|
|
305
310
|
lags: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
306
311
|
metadata: Metadata.optional(),
|
|
307
312
|
})
|
|
308
|
-
.
|
|
313
|
+
.loose();
|
|
309
314
|
const Lag = zod_1.z
|
|
310
315
|
.object({
|
|
311
316
|
id: zod_1.z.string(),
|
|
@@ -316,11 +321,11 @@ const Lag = zod_1.z
|
|
|
316
321
|
deviceId: zod_1.z.string().optional(),
|
|
317
322
|
portIdxs: zod_1.z.array(zod_1.z.number()).optional(),
|
|
318
323
|
})
|
|
319
|
-
.
|
|
324
|
+
.loose())
|
|
320
325
|
.optional(),
|
|
321
326
|
metadata: Metadata.optional(),
|
|
322
327
|
})
|
|
323
|
-
.
|
|
328
|
+
.loose();
|
|
324
329
|
const DnsPolicy = zod_1.z
|
|
325
330
|
.object({
|
|
326
331
|
id: zod_1.z.string(),
|
|
@@ -344,7 +349,7 @@ const DnsPolicy = zod_1.z
|
|
|
344
349
|
ipAddress: zod_1.z.string().nullable().optional(),
|
|
345
350
|
metadata: Metadata.optional(),
|
|
346
351
|
})
|
|
347
|
-
.
|
|
352
|
+
.loose();
|
|
348
353
|
const TrafficMatchingList = zod_1.z
|
|
349
354
|
.object({
|
|
350
355
|
id: zod_1.z.string(),
|
|
@@ -353,7 +358,7 @@ const TrafficMatchingList = zod_1.z
|
|
|
353
358
|
items: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
354
359
|
metadata: Metadata.optional(),
|
|
355
360
|
})
|
|
356
|
-
.
|
|
361
|
+
.loose();
|
|
357
362
|
// Verified against the live Integration API (11.0.83). The WAN and
|
|
358
363
|
// site-to-site-tunnel list rows are intentionally minimal in the API.
|
|
359
364
|
const Wan = zod_1.z
|
|
@@ -361,7 +366,7 @@ const Wan = zod_1.z
|
|
|
361
366
|
id: zod_1.z.string(),
|
|
362
367
|
name: zod_1.z.string().optional(),
|
|
363
368
|
})
|
|
364
|
-
.
|
|
369
|
+
.loose();
|
|
365
370
|
const VpnServer = zod_1.z
|
|
366
371
|
.object({
|
|
367
372
|
id: zod_1.z.string(),
|
|
@@ -370,21 +375,21 @@ const VpnServer = zod_1.z
|
|
|
370
375
|
enabled: zod_1.z.boolean().optional(),
|
|
371
376
|
metadata: Metadata.optional(),
|
|
372
377
|
})
|
|
373
|
-
.
|
|
378
|
+
.loose();
|
|
374
379
|
// No tunnels present in the verification environment — keep loose.
|
|
375
380
|
const VpnTunnel = zod_1.z
|
|
376
381
|
.object({
|
|
377
382
|
id: zod_1.z.string(),
|
|
378
383
|
name: zod_1.z.string().optional(),
|
|
379
384
|
})
|
|
380
|
-
.
|
|
385
|
+
.loose();
|
|
381
386
|
const RadiusProfile = zod_1.z
|
|
382
387
|
.object({
|
|
383
388
|
id: zod_1.z.string(),
|
|
384
389
|
name: zod_1.z.string().optional(),
|
|
385
390
|
metadata: Metadata.optional(),
|
|
386
391
|
})
|
|
387
|
-
.
|
|
392
|
+
.loose();
|
|
388
393
|
// No tags present in the verification environment — documented shape.
|
|
389
394
|
const DeviceTag = zod_1.z
|
|
390
395
|
.object({
|
|
@@ -392,27 +397,27 @@ const DeviceTag = zod_1.z
|
|
|
392
397
|
name: zod_1.z.string().optional(),
|
|
393
398
|
deviceIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
394
399
|
})
|
|
395
|
-
.
|
|
400
|
+
.loose();
|
|
396
401
|
// DPI category/application ids are NUMERIC (verified live).
|
|
397
402
|
const DpiCategory = zod_1.z
|
|
398
403
|
.object({
|
|
399
404
|
id: zod_1.z.number(),
|
|
400
405
|
name: zod_1.z.string().optional(),
|
|
401
406
|
})
|
|
402
|
-
.
|
|
407
|
+
.loose();
|
|
403
408
|
const DpiApplication = zod_1.z
|
|
404
409
|
.object({
|
|
405
410
|
id: zod_1.z.number(),
|
|
406
411
|
name: zod_1.z.string().optional(),
|
|
407
412
|
})
|
|
408
|
-
.
|
|
413
|
+
.loose();
|
|
409
414
|
// Countries key on `code` (ISO alpha-2), NOT `id` (verified live).
|
|
410
415
|
const Country = zod_1.z
|
|
411
416
|
.object({
|
|
412
417
|
code: zod_1.z.string(),
|
|
413
418
|
name: zod_1.z.string().optional(),
|
|
414
419
|
})
|
|
415
|
-
.
|
|
420
|
+
.loose();
|
|
416
421
|
// No pending devices in the verification environment — keep loose.
|
|
417
422
|
const PendingDevice = zod_1.z
|
|
418
423
|
.object({
|
|
@@ -421,7 +426,31 @@ const PendingDevice = zod_1.z
|
|
|
421
426
|
ipAddress: zod_1.z.string().optional(),
|
|
422
427
|
firmwareVersion: zod_1.z.string().optional(),
|
|
423
428
|
})
|
|
424
|
-
.
|
|
429
|
+
.loose();
|
|
430
|
+
// A write tool returns either the API's echo of the resource or, with dryRun,
|
|
431
|
+
// a preview of the request it would send. The SDK validates both against the
|
|
432
|
+
// one outputSchema, so the resource's key fields relax to optional alongside
|
|
433
|
+
// the preview fields; read tools keep their stricter schemas.
|
|
434
|
+
const DryRunPreview = {
|
|
435
|
+
dryRun: zod_1.z.literal(true).optional(),
|
|
436
|
+
wouldExecute: zod_1.z
|
|
437
|
+
.object({ method: zod_1.z.string(), path: zod_1.z.string(), body: zod_1.z.unknown().optional() })
|
|
438
|
+
.loose()
|
|
439
|
+
.optional(),
|
|
440
|
+
confirmationRequired: zod_1.z.string().optional(),
|
|
441
|
+
};
|
|
442
|
+
/**
|
|
443
|
+
* The outputSchema to publish for a tool: always a loose object, whether the
|
|
444
|
+
* tool passed a raw shape or a Zod object (the SDK accepts both). Wrapping a
|
|
445
|
+
* Zod object in z.looseObject() would build a broken schema that makes
|
|
446
|
+
* tools/list throw for every tool.
|
|
447
|
+
*/
|
|
448
|
+
function looseOutput(schema) {
|
|
449
|
+
return schema instanceof zod_1.z.ZodObject ? schema.loose() : zod_1.z.looseObject(schema);
|
|
450
|
+
}
|
|
451
|
+
function writeResult(shape) {
|
|
452
|
+
return { ...zod_1.z.object(shape).partial().shape, ...DryRunPreview };
|
|
453
|
+
}
|
|
425
454
|
exports.getInfoOutputSchema = ApplicationInfo.shape;
|
|
426
455
|
exports.listSitesOutputSchema = {
|
|
427
456
|
...PaginationFields,
|
|
@@ -552,7 +581,7 @@ exports.HistoryClient = zod_1.z
|
|
|
552
581
|
is_wired: zod_1.z.boolean().optional(),
|
|
553
582
|
status: zod_1.z.string().optional(),
|
|
554
583
|
})
|
|
555
|
-
.
|
|
584
|
+
.loose();
|
|
556
585
|
exports.ClientSession = zod_1.z
|
|
557
586
|
.object({
|
|
558
587
|
_id: zod_1.z.string(),
|
|
@@ -565,15 +594,18 @@ exports.ClientSession = zod_1.z
|
|
|
565
594
|
is_wired: zod_1.z.boolean().optional(),
|
|
566
595
|
roaming_sessions: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
567
596
|
})
|
|
568
|
-
.
|
|
597
|
+
.loose();
|
|
569
598
|
/** Envelope returned by the controller's stat/session endpoint. */
|
|
570
599
|
exports.ClientSessionEnvelope = zod_1.z.object({
|
|
571
|
-
meta: zod_1.z.object({ rc: zod_1.z.literal("ok") }).
|
|
600
|
+
meta: zod_1.z.object({ rc: zod_1.z.literal("ok") }).loose(),
|
|
572
601
|
data: zod_1.z.array(exports.ClientSession),
|
|
573
602
|
});
|
|
574
603
|
exports.listClientHistoryOutputSchema = {
|
|
575
604
|
data: zod_1.z.array(exports.HistoryClient),
|
|
576
605
|
count: zod_1.z.number(),
|
|
606
|
+
totalCount: zod_1.z.number(),
|
|
607
|
+
limit: zod_1.z.number(),
|
|
608
|
+
truncated: zod_1.z.boolean(),
|
|
577
609
|
withinHours: zod_1.z.number(),
|
|
578
610
|
coverage: zod_1.z.string(),
|
|
579
611
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* An Integration API resource ID. IDs are interpolated into request paths, so
|
|
4
|
+
* only the plain ID charset is accepted — anything else could smuggle a query
|
|
5
|
+
* (`?force=true`), a fragment, or extra path segments into the request.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resourceId(): z.ZodString;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resourceId = resourceId;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* An Integration API resource ID. IDs are interpolated into request paths, so
|
|
7
|
+
* only the plain ID charset is accepted — anything else could smuggle a query
|
|
8
|
+
* (`?force=true`), a fragment, or extra path segments into the request.
|
|
9
|
+
*/
|
|
10
|
+
function resourceId() {
|
|
11
|
+
return zod_1.z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "Use an ID returned by a list or get tool");
|
|
12
|
+
}
|
|
@@ -7,6 +7,8 @@ export declare function formatSuccess(data: unknown, opts?: {
|
|
|
7
7
|
}[];
|
|
8
8
|
structuredContent?: Record<string, unknown>;
|
|
9
9
|
};
|
|
10
|
+
/** The message of a thrown value, which need not be an Error. */
|
|
11
|
+
export declare function errorMessage(err: unknown): string;
|
|
10
12
|
export declare function formatError(err: unknown): {
|
|
11
13
|
content: {
|
|
12
14
|
type: "text";
|
package/dist/utils/responses.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatSuccess = formatSuccess;
|
|
4
|
+
exports.errorMessage = errorMessage;
|
|
4
5
|
exports.formatError = formatError;
|
|
5
6
|
function formatSuccess(data, opts) {
|
|
6
7
|
const result = {
|
|
7
|
-
content: [{ type: "text", text: JSON.stringify(data
|
|
8
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
8
9
|
};
|
|
9
10
|
if (opts?.structured &&
|
|
10
11
|
data !== null &&
|
|
@@ -14,10 +15,35 @@ function formatSuccess(data, opts) {
|
|
|
14
15
|
}
|
|
15
16
|
return result;
|
|
16
17
|
}
|
|
18
|
+
/** The message of a thrown value, which need not be an Error. */
|
|
19
|
+
function errorMessage(err) {
|
|
20
|
+
return err instanceof Error ? err.message : String(err);
|
|
21
|
+
}
|
|
17
22
|
function formatError(err) {
|
|
18
|
-
|
|
23
|
+
let message = errorMessage(err);
|
|
24
|
+
// fetch reports every network failure as "fetch failed" and keeps the reason
|
|
25
|
+
// (ECONNREFUSED, ENOTFOUND, a certificate error) in cause.
|
|
26
|
+
const cause = err instanceof Error ? err.cause : undefined;
|
|
27
|
+
const code = cause instanceof Error && "code" in cause && typeof cause.code === "string" ? cause.code : undefined;
|
|
28
|
+
if (code) {
|
|
29
|
+
message += ` (${code})`;
|
|
30
|
+
message += tlsHint(code);
|
|
31
|
+
}
|
|
19
32
|
return {
|
|
20
33
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
21
34
|
isError: true,
|
|
22
35
|
};
|
|
23
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* What to do about a certificate that did not verify. Pinning makes a
|
|
39
|
+
* console's own certificate trusted, but it does not lift the validity dates,
|
|
40
|
+
* so an expired certificate gets its own advice.
|
|
41
|
+
*/
|
|
42
|
+
function tlsHint(code) {
|
|
43
|
+
if (code === "CERT_HAS_EXPIRED" || code === "CERT_NOT_YET_VALID") {
|
|
44
|
+
return ". The console's TLS certificate is outside its validity dates; check the clocks on the console and this machine, and renew the certificate if it has expired (pinning it with UNIFI_NETWORK_CA_CERT does not override the dates)";
|
|
45
|
+
}
|
|
46
|
+
if (!/CERT|SELF_SIGNED|LEAF_SIGNATURE/.test(code))
|
|
47
|
+
return "";
|
|
48
|
+
return ". The console's TLS certificate did not verify. For a local console with a self-signed certificate, save the certificate and point UNIFI_NETWORK_CA_CERT at it; if it is already set, the console has a new certificate, so save it again. Against api.ui.com, something between you and Ubiquiti presented this certificate: do not work around it";
|
|
49
|
+
}
|
package/dist/utils/safety.d.ts
CHANGED
|
@@ -1,14 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
/** The confirm input of a DESTRUCTIVE tool. */
|
|
4
|
+
export declare const confirmDestructive: () => z.ZodOptional<z.ZodBoolean>;
|
|
2
5
|
export declare const READ_ONLY: ToolAnnotations;
|
|
3
6
|
export declare const WRITE: ToolAnnotations;
|
|
4
7
|
export declare const WRITE_NOT_IDEMPOTENT: ToolAnnotations;
|
|
5
8
|
export declare const DESTRUCTIVE: ToolAnnotations;
|
|
6
|
-
|
|
9
|
+
/**
|
|
10
|
+
* A dry run's result. confirmationRequired is the reason the real call needs
|
|
11
|
+
* confirm, if it does: the preview is what the user approves, so it has to
|
|
12
|
+
* say that the change opens access before they approve it, not after.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatDryRun(method: string, path: string, body?: unknown, confirmationRequired?: string): {
|
|
7
15
|
content: {
|
|
8
16
|
type: "text";
|
|
9
17
|
text: string;
|
|
10
18
|
}[];
|
|
19
|
+
structuredContent: Record<string, unknown>;
|
|
11
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Confirmation for a write that is routine in general but, with these inputs,
|
|
23
|
+
* opens access — an ALLOW rule, an open SSID, a batch of guest codes. reason
|
|
24
|
+
* says what opens, or is undefined when nothing does; a dry run changes
|
|
25
|
+
* nothing, so it never needs confirming.
|
|
26
|
+
*/
|
|
27
|
+
export declare function requireConfirmationIf(reason: string | undefined, confirm: boolean | undefined, dryRun: boolean | undefined): {
|
|
28
|
+
content: {
|
|
29
|
+
type: "text";
|
|
30
|
+
text: string;
|
|
31
|
+
}[];
|
|
32
|
+
isError: true;
|
|
33
|
+
} | null;
|
|
34
|
+
/** How a rule in an evaluation order treats the traffic it matches. */
|
|
35
|
+
export interface RuleEffect {
|
|
36
|
+
letsThrough: boolean;
|
|
37
|
+
blocks: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether a reorder opens access: whether it moves a rule that lets traffic
|
|
41
|
+
* through ahead of one that blocks, which it used to follow. Only rules in
|
|
42
|
+
* both orders are compared, and effect is asked only about the rules a
|
|
43
|
+
* reorder actually moves past one another. IDs are compared without case.
|
|
44
|
+
*/
|
|
45
|
+
export declare function reorderOpens(before: readonly string[], after: readonly string[], effect: (id: string) => Promise<RuleEffect>): Promise<boolean>;
|
|
12
46
|
export declare function requireConfirmation(confirm: boolean | undefined, action: string): {
|
|
13
47
|
content: {
|
|
14
48
|
type: "text";
|
package/dist/utils/safety.js
CHANGED
|
@@ -1,38 +1,88 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DESTRUCTIVE = exports.WRITE_NOT_IDEMPOTENT = exports.WRITE = exports.READ_ONLY = void 0;
|
|
3
|
+
exports.DESTRUCTIVE = exports.WRITE_NOT_IDEMPOTENT = exports.WRITE = exports.READ_ONLY = exports.confirmDestructive = void 0;
|
|
4
4
|
exports.formatDryRun = formatDryRun;
|
|
5
|
+
exports.requireConfirmationIf = requireConfirmationIf;
|
|
6
|
+
exports.reorderOpens = reorderOpens;
|
|
5
7
|
exports.requireConfirmation = requireConfirmation;
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const write_log_js_1 = require("../write-log.js");
|
|
10
|
+
/** The confirm input of a DESTRUCTIVE tool. */
|
|
11
|
+
const confirmDestructive = () => zod_1.z.boolean().optional().describe("Must be true to execute this destructive action; not needed with dryRun: true");
|
|
12
|
+
exports.confirmDestructive = confirmDestructive;
|
|
6
13
|
exports.READ_ONLY = {
|
|
7
14
|
readOnlyHint: true,
|
|
8
15
|
destructiveHint: false,
|
|
16
|
+
openWorldHint: false,
|
|
9
17
|
};
|
|
10
18
|
exports.WRITE = {
|
|
11
19
|
readOnlyHint: false,
|
|
12
20
|
destructiveHint: false,
|
|
13
21
|
idempotentHint: true,
|
|
22
|
+
openWorldHint: false,
|
|
14
23
|
};
|
|
15
24
|
exports.WRITE_NOT_IDEMPOTENT = {
|
|
16
25
|
readOnlyHint: false,
|
|
17
26
|
destructiveHint: false,
|
|
18
27
|
idempotentHint: false,
|
|
28
|
+
openWorldHint: false,
|
|
19
29
|
};
|
|
20
30
|
exports.DESTRUCTIVE = {
|
|
21
31
|
readOnlyHint: false,
|
|
22
32
|
destructiveHint: true,
|
|
23
33
|
idempotentHint: false,
|
|
34
|
+
openWorldHint: false,
|
|
24
35
|
};
|
|
25
|
-
|
|
36
|
+
/**
|
|
37
|
+
* A dry run's result. confirmationRequired is the reason the real call needs
|
|
38
|
+
* confirm, if it does: the preview is what the user approves, so it has to
|
|
39
|
+
* say that the change opens access before they approve it, not after.
|
|
40
|
+
*/
|
|
41
|
+
function formatDryRun(method, path, body, confirmationRequired) {
|
|
26
42
|
const preview = {
|
|
27
43
|
dryRun: true,
|
|
28
44
|
wouldExecute: { method, path, ...(body !== undefined ? { body } : {}) },
|
|
45
|
+
...(confirmationRequired !== undefined ? { confirmationRequired } : {}),
|
|
29
46
|
};
|
|
47
|
+
// structuredContent is required: the SDK rejects a result without it from
|
|
48
|
+
// any tool that declares an outputSchema.
|
|
30
49
|
return {
|
|
31
|
-
content: [{ type: "text", text: JSON.stringify(preview
|
|
50
|
+
content: [{ type: "text", text: JSON.stringify(preview) }],
|
|
51
|
+
structuredContent: preview,
|
|
32
52
|
};
|
|
33
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Confirmation for a write that is routine in general but, with these inputs,
|
|
56
|
+
* opens access — an ALLOW rule, an open SSID, a batch of guest codes. reason
|
|
57
|
+
* says what opens, or is undefined when nothing does; a dry run changes
|
|
58
|
+
* nothing, so it never needs confirming.
|
|
59
|
+
*/
|
|
60
|
+
function requireConfirmationIf(reason, confirm, dryRun) {
|
|
61
|
+
return reason === undefined || dryRun ? null : requireConfirmation(confirm, reason);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether a reorder opens access: whether it moves a rule that lets traffic
|
|
65
|
+
* through ahead of one that blocks, which it used to follow. Only rules in
|
|
66
|
+
* both orders are compared, and effect is asked only about the rules a
|
|
67
|
+
* reorder actually moves past one another. IDs are compared without case.
|
|
68
|
+
*/
|
|
69
|
+
async function reorderOpens(before, after, effect) {
|
|
70
|
+
const rank = new Map(before.map((id, index) => [id.toLowerCase(), index]));
|
|
71
|
+
const passes = [];
|
|
72
|
+
after.forEach((ahead, i) => {
|
|
73
|
+
for (const behind of after.slice(i + 1)) {
|
|
74
|
+
const [was, other] = [rank.get(ahead.toLowerCase()), rank.get(behind.toLowerCase())];
|
|
75
|
+
if (was !== undefined && other !== undefined && was > other)
|
|
76
|
+
passes.push([ahead, behind]);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const ids = [...new Set(passes.flat())];
|
|
80
|
+
const effects = new Map(await Promise.all(ids.map(async (id) => [id, await effect(id)])));
|
|
81
|
+
return passes.some(([ahead, behind]) => effects.get(ahead)?.letsThrough === true && effects.get(behind)?.blocks === true);
|
|
82
|
+
}
|
|
34
83
|
function requireConfirmation(confirm, action) {
|
|
35
84
|
if (confirm !== true) {
|
|
85
|
+
(0, write_log_js_1.noteRefusal)("confirmation_required");
|
|
36
86
|
return {
|
|
37
87
|
content: [
|
|
38
88
|
{
|
package/dist/utils/secrets.js
CHANGED
|
@@ -79,6 +79,9 @@ function containsRedacted(value) {
|
|
|
79
79
|
function stableStringify(value) {
|
|
80
80
|
if (Array.isArray(value))
|
|
81
81
|
return `[${value.map(stableStringify).join(",")}]`;
|
|
82
|
+
// JSON.stringify returns undefined for undefined and functions, whatever
|
|
83
|
+
// its lib.d.ts signature says.
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
82
85
|
if (value === null || typeof value !== "object")
|
|
83
86
|
return JSON.stringify(value) ?? "undefined";
|
|
84
87
|
const entries = Object.entries(value)
|