@owine/unifi-network-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.
- package/LICENSE +21 -0
- package/README.md +235 -80
- 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 +126 -53
- package/dist/tools/client-history.js +20 -10
- package/dist/tools/clients.js +14 -13
- package/dist/tools/devices.js +30 -29
- package/dist/tools/dns-policies.js +116 -69
- package/dist/tools/firewall.d.ts +2 -1
- package/dist/tools/firewall.js +245 -80
- 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 +152 -58
- package/dist/tools/sites.js +3 -3
- package/dist/tools/supporting.js +31 -30
- package/dist/tools/switching.js +22 -21
- package/dist/tools/system.js +1 -1
- package/dist/tools/traffic-matching.d.ts +2 -1
- package/dist/tools/traffic-matching.js +31 -28
- package/dist/tools/wifi.js +228 -311
- package/dist/utils/full-put.d.ts +32 -0
- package/dist/utils/full-put.js +74 -0
- package/dist/utils/output-schemas.d.ts +77 -9
- package/dist/utils/output-schemas.js +106 -54
- 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.d.ts +24 -0
- package/dist/utils/secrets.js +92 -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 +19 -15
|
@@ -1,20 +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
|
-
* Verified against UniFi Network API
|
|
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");
|
|
20
|
+
// configurable is live-verified (11.0.83) on system-defined zones, policies,
|
|
21
|
+
// networks and RADIUS profiles; it is absent on user-defined resources.
|
|
15
22
|
const Metadata = zod_1.z
|
|
16
|
-
.object({ origin: zod_1.z.string().optional() })
|
|
17
|
-
.
|
|
23
|
+
.object({ origin: zod_1.z.string().optional(), configurable: zod_1.z.boolean().optional() })
|
|
24
|
+
.loose();
|
|
18
25
|
const PaginationFields = {
|
|
19
26
|
offset: zod_1.z.number().int().optional(),
|
|
20
27
|
limit: zod_1.z.number().int().optional(),
|
|
@@ -43,7 +50,7 @@ const DeviceListItem = zod_1.z
|
|
|
43
50
|
features: zod_1.z.array(zod_1.z.string()).optional(),
|
|
44
51
|
interfaces: zod_1.z.array(zod_1.z.string()).optional(),
|
|
45
52
|
})
|
|
46
|
-
.
|
|
53
|
+
.loose();
|
|
47
54
|
const DevicePort = zod_1.z
|
|
48
55
|
.object({
|
|
49
56
|
idx: zod_1.z.number(),
|
|
@@ -59,10 +66,10 @@ const DevicePort = zod_1.z
|
|
|
59
66
|
enabled: zod_1.z.boolean().optional(),
|
|
60
67
|
state: zod_1.z.string().optional(),
|
|
61
68
|
})
|
|
62
|
-
.
|
|
69
|
+
.loose()
|
|
63
70
|
.optional(),
|
|
64
71
|
})
|
|
65
|
-
.
|
|
72
|
+
.loose();
|
|
66
73
|
const DeviceRadio = zod_1.z
|
|
67
74
|
.object({
|
|
68
75
|
wlanStandard: zod_1.z.string().optional(),
|
|
@@ -70,7 +77,7 @@ const DeviceRadio = zod_1.z
|
|
|
70
77
|
channelWidthMHz: zod_1.z.number().optional(),
|
|
71
78
|
channel: zod_1.z.number().optional(),
|
|
72
79
|
})
|
|
73
|
-
.
|
|
80
|
+
.loose();
|
|
74
81
|
const DeviceDetail = zod_1.z
|
|
75
82
|
.object({
|
|
76
83
|
...DeviceCommon,
|
|
@@ -79,7 +86,7 @@ const DeviceDetail = zod_1.z
|
|
|
79
86
|
configurationId: zod_1.z.string().optional(),
|
|
80
87
|
uplink: zod_1.z
|
|
81
88
|
.object({ deviceId: zod_1.z.string().optional() })
|
|
82
|
-
.
|
|
89
|
+
.loose()
|
|
83
90
|
.optional(),
|
|
84
91
|
// Detail view: object keyed by capability (switching/accessPoint).
|
|
85
92
|
features: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
@@ -88,10 +95,10 @@ const DeviceDetail = zod_1.z
|
|
|
88
95
|
ports: zod_1.z.array(DevicePort).optional(),
|
|
89
96
|
radios: zod_1.z.array(DeviceRadio).optional(),
|
|
90
97
|
})
|
|
91
|
-
.
|
|
98
|
+
.loose()
|
|
92
99
|
.optional(),
|
|
93
100
|
})
|
|
94
|
-
.
|
|
101
|
+
.loose();
|
|
95
102
|
const DeviceStatistics = zod_1.z
|
|
96
103
|
.object({
|
|
97
104
|
uptimeSec: zod_1.z.number().optional(),
|
|
@@ -107,7 +114,7 @@ const DeviceStatistics = zod_1.z
|
|
|
107
114
|
txRateBps: zod_1.z.number().optional(),
|
|
108
115
|
rxRateBps: zod_1.z.number().optional(),
|
|
109
116
|
})
|
|
110
|
-
.
|
|
117
|
+
.loose()
|
|
111
118
|
.optional(),
|
|
112
119
|
interfaces: zod_1.z
|
|
113
120
|
.object({
|
|
@@ -117,13 +124,13 @@ const DeviceStatistics = zod_1.z
|
|
|
117
124
|
frequencyGHz: zod_1.z.number().optional(),
|
|
118
125
|
txRetriesPct: zod_1.z.number().optional(),
|
|
119
126
|
})
|
|
120
|
-
.
|
|
127
|
+
.loose())
|
|
121
128
|
.optional(),
|
|
122
129
|
})
|
|
123
|
-
.
|
|
130
|
+
.loose()
|
|
124
131
|
.optional(),
|
|
125
132
|
})
|
|
126
|
-
.
|
|
133
|
+
.loose();
|
|
127
134
|
const Client = zod_1.z
|
|
128
135
|
.object({
|
|
129
136
|
id: zod_1.z.string(),
|
|
@@ -135,10 +142,10 @@ const Client = zod_1.z
|
|
|
135
142
|
uplinkDeviceId: zod_1.z.string().optional(),
|
|
136
143
|
access: zod_1.z
|
|
137
144
|
.object({ type: zod_1.z.string().optional() })
|
|
138
|
-
.
|
|
145
|
+
.loose()
|
|
139
146
|
.optional(),
|
|
140
147
|
})
|
|
141
|
-
.
|
|
148
|
+
.loose();
|
|
142
149
|
const WifiBroadcast = zod_1.z
|
|
143
150
|
.object({
|
|
144
151
|
id: zod_1.z.string(),
|
|
@@ -157,20 +164,20 @@ const WifiBroadcast = zod_1.z
|
|
|
157
164
|
broadcastingDeviceFilter: zod_1.z.unknown().optional(),
|
|
158
165
|
metadata: Metadata.optional(),
|
|
159
166
|
})
|
|
160
|
-
.
|
|
167
|
+
.loose();
|
|
161
168
|
const ApplicationInfo = zod_1.z
|
|
162
169
|
.object({
|
|
163
|
-
// Live-verified (
|
|
170
|
+
// Live-verified (11.0.83): only applicationVersion is returned.
|
|
164
171
|
applicationVersion: zod_1.z.string().optional(),
|
|
165
172
|
})
|
|
166
|
-
.
|
|
173
|
+
.loose();
|
|
167
174
|
const Site = zod_1.z
|
|
168
175
|
.object({
|
|
169
176
|
id: zod_1.z.string(),
|
|
170
177
|
internalReference: zod_1.z.string().optional(),
|
|
171
178
|
name: zod_1.z.string().optional(),
|
|
172
179
|
})
|
|
173
|
-
.
|
|
180
|
+
.loose();
|
|
174
181
|
const Network = zod_1.z
|
|
175
182
|
.object({
|
|
176
183
|
id: zod_1.z.string(),
|
|
@@ -190,12 +197,12 @@ const Network = zod_1.z
|
|
|
190
197
|
ipv6Configuration: zod_1.z.unknown().optional(),
|
|
191
198
|
metadata: Metadata.optional(),
|
|
192
199
|
})
|
|
193
|
-
.
|
|
200
|
+
.loose();
|
|
194
201
|
const NetworkReferences = zod_1.z
|
|
195
202
|
.object({
|
|
196
203
|
referenceResources: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
197
204
|
})
|
|
198
|
-
.
|
|
205
|
+
.loose();
|
|
199
206
|
const Voucher = zod_1.z
|
|
200
207
|
.object({
|
|
201
208
|
id: zod_1.z.string(),
|
|
@@ -212,7 +219,7 @@ const Voucher = zod_1.z
|
|
|
212
219
|
authorizedGuestCount: zod_1.z.number().optional(),
|
|
213
220
|
expired: zod_1.z.boolean().optional(),
|
|
214
221
|
})
|
|
215
|
-
.
|
|
222
|
+
.loose();
|
|
216
223
|
const FirewallZone = zod_1.z
|
|
217
224
|
.object({
|
|
218
225
|
id: zod_1.z.string(),
|
|
@@ -220,7 +227,7 @@ const FirewallZone = zod_1.z
|
|
|
220
227
|
networkIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
221
228
|
metadata: Metadata.optional(),
|
|
222
229
|
})
|
|
223
|
-
.
|
|
230
|
+
.loose();
|
|
224
231
|
const FirewallPolicy = zod_1.z
|
|
225
232
|
.object({
|
|
226
233
|
id: zod_1.z.string(),
|
|
@@ -236,11 +243,13 @@ const FirewallPolicy = zod_1.z
|
|
|
236
243
|
ipsecFilter: zod_1.z.unknown().optional(),
|
|
237
244
|
schedule: zod_1.z.unknown().optional(),
|
|
238
245
|
loggingEnabled: zod_1.z.boolean().optional(),
|
|
246
|
+
// New in 11.0.83; live-verified as a boolean on every policy.
|
|
247
|
+
reportFlowEnabled: zod_1.z.boolean().optional(),
|
|
239
248
|
index: zod_1.z.number().optional(),
|
|
240
249
|
description: zod_1.z.string().optional(),
|
|
241
250
|
metadata: Metadata.optional(),
|
|
242
251
|
})
|
|
243
|
-
.
|
|
252
|
+
.loose();
|
|
244
253
|
const FirewallPolicyOrdering = zod_1.z
|
|
245
254
|
.object({
|
|
246
255
|
orderedFirewallPolicyIds: zod_1.z
|
|
@@ -248,10 +257,10 @@ const FirewallPolicyOrdering = zod_1.z
|
|
|
248
257
|
beforeSystemDefined: zod_1.z.array(zod_1.z.string()).optional(),
|
|
249
258
|
afterSystemDefined: zod_1.z.array(zod_1.z.string()).optional(),
|
|
250
259
|
})
|
|
251
|
-
.
|
|
260
|
+
.loose()
|
|
252
261
|
.optional(),
|
|
253
262
|
})
|
|
254
|
-
.
|
|
263
|
+
.loose();
|
|
255
264
|
const AclRule = zod_1.z
|
|
256
265
|
.object({
|
|
257
266
|
id: zod_1.z.string(),
|
|
@@ -259,8 +268,11 @@ const AclRule = zod_1.z
|
|
|
259
268
|
name: zod_1.z.string().optional(),
|
|
260
269
|
enabled: zod_1.z.boolean().optional(),
|
|
261
270
|
action: zod_1.z.string().optional(),
|
|
262
|
-
|
|
263
|
-
|
|
271
|
+
// The docs give null a meaning ("When null, the rule will be applied to
|
|
272
|
+
// all protocols") and ACL samples already carry explicit nulls, so both
|
|
273
|
+
// must accept null or a protocol-agnostic rule hard-fails with -32602.
|
|
274
|
+
description: zod_1.z.string().nullable().optional(),
|
|
275
|
+
protocolFilter: zod_1.z.array(zod_1.z.string()).nullable().optional(),
|
|
264
276
|
sourceFilter: zod_1.z.unknown().optional(),
|
|
265
277
|
destinationFilter: zod_1.z.unknown().optional(),
|
|
266
278
|
enforcingDeviceFilter: zod_1.z.unknown().optional(),
|
|
@@ -269,12 +281,12 @@ const AclRule = zod_1.z
|
|
|
269
281
|
index: zod_1.z.number().int().optional(),
|
|
270
282
|
metadata: Metadata.optional(),
|
|
271
283
|
})
|
|
272
|
-
.
|
|
284
|
+
.loose();
|
|
273
285
|
const AclRuleOrdering = zod_1.z
|
|
274
286
|
.object({
|
|
275
287
|
orderedAclRuleIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
276
288
|
})
|
|
277
|
-
.
|
|
289
|
+
.loose();
|
|
278
290
|
const SwitchStack = zod_1.z
|
|
279
291
|
.object({
|
|
280
292
|
id: zod_1.z.string(),
|
|
@@ -289,7 +301,7 @@ const SwitchStack = zod_1.z
|
|
|
289
301
|
lags: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
290
302
|
metadata: Metadata.optional(),
|
|
291
303
|
})
|
|
292
|
-
.
|
|
304
|
+
.loose();
|
|
293
305
|
const McLagDomain = zod_1.z
|
|
294
306
|
.object({
|
|
295
307
|
id: zod_1.z.string(),
|
|
@@ -298,7 +310,7 @@ const McLagDomain = zod_1.z
|
|
|
298
310
|
lags: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
299
311
|
metadata: Metadata.optional(),
|
|
300
312
|
})
|
|
301
|
-
.
|
|
313
|
+
.loose();
|
|
302
314
|
const Lag = zod_1.z
|
|
303
315
|
.object({
|
|
304
316
|
id: zod_1.z.string(),
|
|
@@ -309,11 +321,11 @@ const Lag = zod_1.z
|
|
|
309
321
|
deviceId: zod_1.z.string().optional(),
|
|
310
322
|
portIdxs: zod_1.z.array(zod_1.z.number()).optional(),
|
|
311
323
|
})
|
|
312
|
-
.
|
|
324
|
+
.loose())
|
|
313
325
|
.optional(),
|
|
314
326
|
metadata: Metadata.optional(),
|
|
315
327
|
})
|
|
316
|
-
.
|
|
328
|
+
.loose();
|
|
317
329
|
const DnsPolicy = zod_1.z
|
|
318
330
|
.object({
|
|
319
331
|
id: zod_1.z.string(),
|
|
@@ -322,9 +334,22 @@ const DnsPolicy = zod_1.z
|
|
|
322
334
|
domain: zod_1.z.string().optional(),
|
|
323
335
|
ipv4Address: zod_1.z.string().optional(),
|
|
324
336
|
ttlSeconds: zod_1.z.number().optional(),
|
|
337
|
+
// Type-specific fields from the 11.0.83 response samples (no live DNS
|
|
338
|
+
// policies to sample), so nullable to be safe.
|
|
339
|
+
ipv6Address: zod_1.z.string().nullable().optional(),
|
|
340
|
+
targetDomain: zod_1.z.string().nullable().optional(),
|
|
341
|
+
mailServerDomain: zod_1.z.string().nullable().optional(),
|
|
342
|
+
text: zod_1.z.string().nullable().optional(),
|
|
343
|
+
serverDomain: zod_1.z.string().nullable().optional(),
|
|
344
|
+
service: zod_1.z.string().nullable().optional(),
|
|
345
|
+
protocol: zod_1.z.string().nullable().optional(),
|
|
346
|
+
port: zod_1.z.number().nullable().optional(),
|
|
347
|
+
priority: zod_1.z.number().nullable().optional(),
|
|
348
|
+
weight: zod_1.z.number().nullable().optional(),
|
|
349
|
+
ipAddress: zod_1.z.string().nullable().optional(),
|
|
325
350
|
metadata: Metadata.optional(),
|
|
326
351
|
})
|
|
327
|
-
.
|
|
352
|
+
.loose();
|
|
328
353
|
const TrafficMatchingList = zod_1.z
|
|
329
354
|
.object({
|
|
330
355
|
id: zod_1.z.string(),
|
|
@@ -333,15 +358,15 @@ const TrafficMatchingList = zod_1.z
|
|
|
333
358
|
items: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
334
359
|
metadata: Metadata.optional(),
|
|
335
360
|
})
|
|
336
|
-
.
|
|
337
|
-
// Verified against the live Integration API (
|
|
361
|
+
.loose();
|
|
362
|
+
// Verified against the live Integration API (11.0.83). The WAN and
|
|
338
363
|
// site-to-site-tunnel list rows are intentionally minimal in the API.
|
|
339
364
|
const Wan = zod_1.z
|
|
340
365
|
.object({
|
|
341
366
|
id: zod_1.z.string(),
|
|
342
367
|
name: zod_1.z.string().optional(),
|
|
343
368
|
})
|
|
344
|
-
.
|
|
369
|
+
.loose();
|
|
345
370
|
const VpnServer = zod_1.z
|
|
346
371
|
.object({
|
|
347
372
|
id: zod_1.z.string(),
|
|
@@ -350,21 +375,21 @@ const VpnServer = zod_1.z
|
|
|
350
375
|
enabled: zod_1.z.boolean().optional(),
|
|
351
376
|
metadata: Metadata.optional(),
|
|
352
377
|
})
|
|
353
|
-
.
|
|
378
|
+
.loose();
|
|
354
379
|
// No tunnels present in the verification environment — keep loose.
|
|
355
380
|
const VpnTunnel = zod_1.z
|
|
356
381
|
.object({
|
|
357
382
|
id: zod_1.z.string(),
|
|
358
383
|
name: zod_1.z.string().optional(),
|
|
359
384
|
})
|
|
360
|
-
.
|
|
385
|
+
.loose();
|
|
361
386
|
const RadiusProfile = zod_1.z
|
|
362
387
|
.object({
|
|
363
388
|
id: zod_1.z.string(),
|
|
364
389
|
name: zod_1.z.string().optional(),
|
|
365
390
|
metadata: Metadata.optional(),
|
|
366
391
|
})
|
|
367
|
-
.
|
|
392
|
+
.loose();
|
|
368
393
|
// No tags present in the verification environment — documented shape.
|
|
369
394
|
const DeviceTag = zod_1.z
|
|
370
395
|
.object({
|
|
@@ -372,27 +397,27 @@ const DeviceTag = zod_1.z
|
|
|
372
397
|
name: zod_1.z.string().optional(),
|
|
373
398
|
deviceIds: zod_1.z.array(zod_1.z.string()).optional(),
|
|
374
399
|
})
|
|
375
|
-
.
|
|
400
|
+
.loose();
|
|
376
401
|
// DPI category/application ids are NUMERIC (verified live).
|
|
377
402
|
const DpiCategory = zod_1.z
|
|
378
403
|
.object({
|
|
379
404
|
id: zod_1.z.number(),
|
|
380
405
|
name: zod_1.z.string().optional(),
|
|
381
406
|
})
|
|
382
|
-
.
|
|
407
|
+
.loose();
|
|
383
408
|
const DpiApplication = zod_1.z
|
|
384
409
|
.object({
|
|
385
410
|
id: zod_1.z.number(),
|
|
386
411
|
name: zod_1.z.string().optional(),
|
|
387
412
|
})
|
|
388
|
-
.
|
|
413
|
+
.loose();
|
|
389
414
|
// Countries key on `code` (ISO alpha-2), NOT `id` (verified live).
|
|
390
415
|
const Country = zod_1.z
|
|
391
416
|
.object({
|
|
392
417
|
code: zod_1.z.string(),
|
|
393
418
|
name: zod_1.z.string().optional(),
|
|
394
419
|
})
|
|
395
|
-
.
|
|
420
|
+
.loose();
|
|
396
421
|
// No pending devices in the verification environment — keep loose.
|
|
397
422
|
const PendingDevice = zod_1.z
|
|
398
423
|
.object({
|
|
@@ -401,7 +426,31 @@ const PendingDevice = zod_1.z
|
|
|
401
426
|
ipAddress: zod_1.z.string().optional(),
|
|
402
427
|
firmwareVersion: zod_1.z.string().optional(),
|
|
403
428
|
})
|
|
404
|
-
.
|
|
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
|
+
}
|
|
405
454
|
exports.getInfoOutputSchema = ApplicationInfo.shape;
|
|
406
455
|
exports.listSitesOutputSchema = {
|
|
407
456
|
...PaginationFields,
|
|
@@ -532,7 +581,7 @@ exports.HistoryClient = zod_1.z
|
|
|
532
581
|
is_wired: zod_1.z.boolean().optional(),
|
|
533
582
|
status: zod_1.z.string().optional(),
|
|
534
583
|
})
|
|
535
|
-
.
|
|
584
|
+
.loose();
|
|
536
585
|
exports.ClientSession = zod_1.z
|
|
537
586
|
.object({
|
|
538
587
|
_id: zod_1.z.string(),
|
|
@@ -545,15 +594,18 @@ exports.ClientSession = zod_1.z
|
|
|
545
594
|
is_wired: zod_1.z.boolean().optional(),
|
|
546
595
|
roaming_sessions: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
547
596
|
})
|
|
548
|
-
.
|
|
597
|
+
.loose();
|
|
549
598
|
/** Envelope returned by the controller's stat/session endpoint. */
|
|
550
599
|
exports.ClientSessionEnvelope = zod_1.z.object({
|
|
551
|
-
meta: zod_1.z.object({ rc: zod_1.z.literal("ok") }).
|
|
600
|
+
meta: zod_1.z.object({ rc: zod_1.z.literal("ok") }).loose(),
|
|
552
601
|
data: zod_1.z.array(exports.ClientSession),
|
|
553
602
|
});
|
|
554
603
|
exports.listClientHistoryOutputSchema = {
|
|
555
604
|
data: zod_1.z.array(exports.HistoryClient),
|
|
556
605
|
count: zod_1.z.number(),
|
|
606
|
+
totalCount: zod_1.z.number(),
|
|
607
|
+
limit: zod_1.z.number(),
|
|
608
|
+
truncated: zod_1.z.boolean(),
|
|
557
609
|
withinHours: zod_1.z.number(),
|
|
558
610
|
coverage: zod_1.z.string(),
|
|
559
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
|
{
|
|
@@ -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;
|