@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
package/dist/tools/devices.js
CHANGED
|
@@ -6,27 +6,28 @@ const responses_js_1 = require("../utils/responses.js");
|
|
|
6
6
|
const query_js_1 = require("../utils/query.js");
|
|
7
7
|
const safety_js_1 = require("../utils/safety.js");
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
|
+
const params_js_1 = require("../utils/params.js");
|
|
9
10
|
// Adopt echoes the adopted device — reuse the device detail schema.
|
|
10
11
|
const adoptDeviceOutputSchema = output_schemas_js_1.getDeviceOutputSchema;
|
|
11
12
|
function registerDeviceTools(server, client, readOnly = false) {
|
|
12
13
|
server.registerTool("unifi_list_devices", {
|
|
13
14
|
description: "List all adopted devices (gateways, switches, APs) at a site. Returns: id, name, model, macAddress, ipAddress, state (ONLINE/OFFLINE/etc), supported, firmwareVersion, firmwareUpdatable, features[] (capability tags, e.g. ['switching'] or ['accessPoint']), interfaces[] (e.g. ['ports'] or ['radios']). NOTE: features/interfaces are string arrays here; unifi_get_device expands them into objects. Use for: device inventory; pair with unifi_get_device for full config (port table, radios) and unifi_get_device_statistics for live metrics.",
|
|
14
15
|
inputSchema: {
|
|
15
|
-
siteId:
|
|
16
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
16
17
|
offset: zod_1.z
|
|
17
18
|
.number()
|
|
18
19
|
.int()
|
|
19
20
|
.nonnegative()
|
|
20
21
|
.optional()
|
|
21
|
-
.describe("Number of records to skip
|
|
22
|
+
.describe("Number of records to skip"),
|
|
22
23
|
limit: zod_1.z
|
|
23
24
|
.number()
|
|
24
25
|
.int()
|
|
25
26
|
.min(0)
|
|
26
27
|
.max(200)
|
|
27
28
|
.optional()
|
|
28
|
-
.describe("Number of records to return (default: 25
|
|
29
|
-
filter: zod_1.z.string().optional().describe("Filter expression"),
|
|
29
|
+
.describe("Number of records to return (API default: 25)"),
|
|
30
|
+
filter: zod_1.z.string().optional().describe("Filter expression. Filterable: name (supports like), model, state, macAddress, ipAddress, firmwareVersion, supported, firmwareUpdatable, features/interfaces (sets: contains/containsAny/containsAll). like is rejected on model. Example: features.contains('accessPoint')"),
|
|
30
31
|
},
|
|
31
32
|
outputSchema: output_schemas_js_1.listDevicesOutputSchema,
|
|
32
33
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -41,10 +42,10 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
41
42
|
}
|
|
42
43
|
});
|
|
43
44
|
server.registerTool("unifi_get_device", {
|
|
44
|
-
description: "Get full configuration for a device. Returns (in addition to list fields):
|
|
45
|
+
description: "Get full configuration for a device. Returns (in addition to list fields): adoptedAt, provisionedAt, configurationId, uplink.deviceId (absent on the ISP modem), features (object keyed by capability: switching {lags[]} / accessPoint {}), interfaces.ports[] for switches, gateways and modems ({idx, state, connector, maxSpeedMbps, speedMbps (may be omitted on DOWN ports), poe:{standard, type, enabled, state} on PoE ports only}), interfaces.radios[] for APs ({wlanStandard, frequencyGHz, channelWidthMHz, channel}). Some devices (e.g. 5G modems) return empty features/interfaces objects. NOTE: in the LIST endpoint, features/interfaces are capability-tag string arrays instead. Use for: switch port layout/PoE state, AP radio config, uplink topology. For live throughput/CPU/memory, use unifi_get_device_statistics.",
|
|
45
46
|
inputSchema: {
|
|
46
|
-
siteId:
|
|
47
|
-
deviceId:
|
|
47
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
48
|
+
deviceId: (0, params_js_1.resourceId)().describe("Device ID"),
|
|
48
49
|
},
|
|
49
50
|
outputSchema: output_schemas_js_1.getDeviceOutputSchema,
|
|
50
51
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -58,10 +59,10 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
58
59
|
}
|
|
59
60
|
});
|
|
60
61
|
server.registerTool("unifi_get_device_statistics", {
|
|
61
|
-
description: "Get latest live statistics for a device. Returns: uptimeSec, lastHeartbeatAt, nextHeartbeatAt, loadAverage1/5/15Min, cpuUtilizationPct, memoryUtilizationPct, uplink (txRateBps, rxRateBps), interfaces.radios[] for APs ({frequencyGHz, txRetriesPct}). NOTE: verified against
|
|
62
|
+
description: "Get latest live statistics for a device. Returns: uptimeSec, lastHeartbeatAt, nextHeartbeatAt, loadAverage1/5/15Min, cpuUtilizationPct, memoryUtilizationPct, uplink (txRateBps, rxRateBps; absent on e.g. the ISP modem), interfaces.radios[] for APs ({frequencyGHz, txRetriesPct}; interfaces is {} for non-AP devices). NOTE: verified against 11.0.83 — the Integration API does NOT expose per-switch-port byte/error/PoE-power counters here; port-level live stats are unavailable. Use for: device health and AP radio metrics. For config (channel, power, port assignment), use unifi_get_device.",
|
|
62
63
|
inputSchema: {
|
|
63
|
-
siteId:
|
|
64
|
-
deviceId:
|
|
64
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
65
|
+
deviceId: (0, params_js_1.resourceId)().describe("Device ID"),
|
|
65
66
|
},
|
|
66
67
|
outputSchema: output_schemas_js_1.getDeviceStatisticsOutputSchema,
|
|
67
68
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -75,22 +76,22 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
server.registerTool("unifi_list_pending_devices", {
|
|
78
|
-
description: "List devices pending adoption across all sites (global endpoint, not site-scoped). Returns: basic device info per pending device (macAddress, model, ipAddress, firmwareVersion, etc. — exact per-row schema is not rendered in the
|
|
79
|
+
description: "List devices pending adoption across all sites (global endpoint, not site-scoped). Returns: basic device info per pending device (macAddress, model, ipAddress, firmwareVersion, etc. — exact per-row schema is not rendered in the 11.0.83 docs). Use for: discovering new devices on the network before calling unifi_adopt_device.",
|
|
79
80
|
inputSchema: {
|
|
80
81
|
offset: zod_1.z
|
|
81
82
|
.number()
|
|
82
83
|
.int()
|
|
83
84
|
.nonnegative()
|
|
84
85
|
.optional()
|
|
85
|
-
.describe("Number of records to skip
|
|
86
|
+
.describe("Number of records to skip"),
|
|
86
87
|
limit: zod_1.z
|
|
87
88
|
.number()
|
|
88
89
|
.int()
|
|
89
90
|
.min(0)
|
|
90
91
|
.max(200)
|
|
91
92
|
.optional()
|
|
92
|
-
.describe("Number of records to return (default: 25
|
|
93
|
-
filter: zod_1.z.string().optional().describe("Filter expression"),
|
|
93
|
+
.describe("Number of records to return (API default: 25)"),
|
|
94
|
+
filter: zod_1.z.string().optional().describe("Filter expression, e.g. model.eq('U7 Pro')"),
|
|
94
95
|
},
|
|
95
96
|
outputSchema: output_schemas_js_1.listPendingDevicesOutputSchema,
|
|
96
97
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -109,14 +110,14 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
109
110
|
server.registerTool("unifi_adopt_device", {
|
|
110
111
|
description: "Adopt a pending device into a site by MAC address. The device must already appear in unifi_list_pending_devices. Idempotency: not safe to retry — re-adopting may error or duplicate.",
|
|
111
112
|
inputSchema: {
|
|
112
|
-
siteId:
|
|
113
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
113
114
|
macAddress: zod_1.z.string().describe("MAC address of the device"),
|
|
114
115
|
// Required by the API; kept optional here and defaulted to false below
|
|
115
116
|
// so callers need not pass it explicitly.
|
|
116
117
|
ignoreDeviceLimit: zod_1.z.boolean().optional().describe("Ignore device limit when adopting (required by the API; sent as false when omitted)"),
|
|
117
118
|
dryRun: zod_1.z.boolean().optional().describe("Preview this action without executing it"),
|
|
118
119
|
},
|
|
119
|
-
outputSchema: adoptDeviceOutputSchema,
|
|
120
|
+
outputSchema: (0, output_schemas_js_1.writeResult)(adoptDeviceOutputSchema),
|
|
120
121
|
annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
|
|
121
122
|
}, async ({ siteId, macAddress, ignoreDeviceLimit, dryRun }) => {
|
|
122
123
|
const body = {
|
|
@@ -136,18 +137,18 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
136
137
|
server.registerTool("unifi_remove_device", {
|
|
137
138
|
description: "DESTRUCTIVE: Remove (unadopt) a device from a site. If the device is online, it will be reset to factory defaults",
|
|
138
139
|
inputSchema: {
|
|
139
|
-
siteId:
|
|
140
|
-
deviceId:
|
|
141
|
-
confirm:
|
|
140
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
141
|
+
deviceId: (0, params_js_1.resourceId)().describe("Device ID"),
|
|
142
|
+
confirm: (0, safety_js_1.confirmDestructive)(),
|
|
142
143
|
dryRun: zod_1.z.boolean().optional().describe("Preview this action without executing it"),
|
|
143
144
|
},
|
|
144
145
|
annotations: safety_js_1.DESTRUCTIVE,
|
|
145
146
|
}, async ({ siteId, deviceId, confirm, dryRun }) => {
|
|
146
|
-
const guard = (0, safety_js_1.requireConfirmation)(confirm, "This will remove the device from the site. If the device is online, it will be reset to factory defaults");
|
|
147
|
+
const guard = dryRun ? null : (0, safety_js_1.requireConfirmation)(confirm, "This will remove the device from the site. If the device is online, it will be reset to factory defaults");
|
|
147
148
|
if (guard)
|
|
148
149
|
return guard;
|
|
149
150
|
if (dryRun)
|
|
150
|
-
return (0, safety_js_1.formatDryRun)("DELETE", `/sites/${siteId}/devices/${deviceId}
|
|
151
|
+
return (0, safety_js_1.formatDryRun)("DELETE", `/sites/${siteId}/devices/${deviceId}`);
|
|
151
152
|
try {
|
|
152
153
|
const data = await client.delete(`/sites/${siteId}/devices/${deviceId}`);
|
|
153
154
|
return (0, responses_js_1.formatSuccess)(data);
|
|
@@ -157,13 +158,13 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
157
158
|
}
|
|
158
159
|
});
|
|
159
160
|
server.registerTool("unifi_restart_device", {
|
|
160
|
-
description: "Restart (reboot) a device. The device will be unreachable for ~1–3 minutes.
|
|
161
|
+
description: "Restart (reboot) a device. The device will be unreachable for ~1–3 minutes. Not idempotent: each call triggers a fresh reboot.",
|
|
161
162
|
inputSchema: {
|
|
162
|
-
siteId:
|
|
163
|
-
deviceId:
|
|
163
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
164
|
+
deviceId: (0, params_js_1.resourceId)().describe("Device ID"),
|
|
164
165
|
dryRun: zod_1.z.boolean().optional().describe("Preview this action without executing it"),
|
|
165
166
|
},
|
|
166
|
-
annotations: safety_js_1.
|
|
167
|
+
annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
|
|
167
168
|
}, async ({ siteId, deviceId, dryRun }) => {
|
|
168
169
|
const body = { action: "RESTART" };
|
|
169
170
|
if (dryRun)
|
|
@@ -179,12 +180,12 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
179
180
|
server.registerTool("unifi_power_cycle_port", {
|
|
180
181
|
description: "Power-cycle PoE on a specific switch port (briefly drops then restores power). portIdx is the port number (1-based) as shown in unifi_get_device interfaces.ports[].idx. Use for: rebooting a PoE-powered camera/AP without touching the device.",
|
|
181
182
|
inputSchema: {
|
|
182
|
-
siteId:
|
|
183
|
-
deviceId:
|
|
184
|
-
portIdx: zod_1.z.number().int().describe("Port index number"),
|
|
183
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
184
|
+
deviceId: (0, params_js_1.resourceId)().describe("Device ID"),
|
|
185
|
+
portIdx: zod_1.z.number().int().min(1).describe("Port index number (1-based)"),
|
|
185
186
|
dryRun: zod_1.z.boolean().optional().describe("Preview this action without executing it"),
|
|
186
187
|
},
|
|
187
|
-
annotations: safety_js_1.
|
|
188
|
+
annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
|
|
188
189
|
}, async ({ siteId, deviceId, portIdx, dryRun }) => {
|
|
189
190
|
const body = { action: "POWER_CYCLE" };
|
|
190
191
|
if (dryRun)
|
|
@@ -5,29 +5,103 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const responses_js_1 = require("../utils/responses.js");
|
|
6
6
|
const query_js_1 = require("../utils/query.js");
|
|
7
7
|
const safety_js_1 = require("../utils/safety.js");
|
|
8
|
+
const full_put_js_1 = require("../utils/full-put.js");
|
|
8
9
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
10
|
+
const params_js_1 = require("../utils/params.js");
|
|
11
|
+
// Type-specific request-body fields, from the 11.0.83 POST/PUT schemas (every
|
|
12
|
+
// type also takes type, enabled, domain). MX/TXT/SRV/FORWARD_DOMAIN have no TTL.
|
|
13
|
+
const DNS_TYPE_FIELDS = {
|
|
14
|
+
A_RECORD: ["ipv4Address", "ttlSeconds"],
|
|
15
|
+
AAAA_RECORD: ["ipv6Address", "ttlSeconds"],
|
|
16
|
+
CNAME_RECORD: ["targetDomain", "ttlSeconds"],
|
|
17
|
+
MX_RECORD: ["mailServerDomain", "priority"],
|
|
18
|
+
TXT_RECORD: ["text"],
|
|
19
|
+
SRV_RECORD: ["serverDomain", "service", "protocol", "port", "priority", "weight"],
|
|
20
|
+
FORWARD_DOMAIN: ["ipAddress"],
|
|
21
|
+
};
|
|
22
|
+
const DNS_TYPES_DOC = "A_RECORD: ipv4Address, ttlSeconds (0-86400). AAAA_RECORD: ipv6Address, ttlSeconds (0-86400). CNAME_RECORD: targetDomain, ttlSeconds (0-604800). MX_RECORD: mailServerDomain, priority. TXT_RECORD: text. SRV_RECORD: serverDomain, service (e.g. _ldap), protocol (e.g. _tcp), port, priority, weight. FORWARD_DOMAIN: ipAddress (upstream DNS server). All type-specific fields are required for their type.";
|
|
23
|
+
const dnsType = zod_1.z
|
|
24
|
+
.enum([
|
|
25
|
+
"A_RECORD",
|
|
26
|
+
"AAAA_RECORD",
|
|
27
|
+
"CNAME_RECORD",
|
|
28
|
+
"MX_RECORD",
|
|
29
|
+
"TXT_RECORD",
|
|
30
|
+
"SRV_RECORD",
|
|
31
|
+
"FORWARD_DOMAIN",
|
|
32
|
+
])
|
|
33
|
+
.describe("DNS record type");
|
|
34
|
+
function fieldsForDnsType(type) {
|
|
35
|
+
if (typeof type !== "string" || !(type in DNS_TYPE_FIELDS)) {
|
|
36
|
+
throw new Error(`Unsupported DNS policy type: ${String(type)}`);
|
|
37
|
+
}
|
|
38
|
+
const specific = DNS_TYPE_FIELDS[type];
|
|
39
|
+
return {
|
|
40
|
+
allowed: ["type", "enabled", "domain", ...specific],
|
|
41
|
+
required: ["type", "enabled", "domain", ...specific],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function checkDnsBody(body, updates) {
|
|
45
|
+
const { allowed, required } = fieldsForDnsType(body.type);
|
|
46
|
+
const rejected = (0, full_put_js_1.unsupportedFields)(updates, allowed);
|
|
47
|
+
if (rejected.length > 0) {
|
|
48
|
+
throw new Error(`Fields not accepted for type ${String(body.type)}: ${rejected.join(", ")}`);
|
|
49
|
+
}
|
|
50
|
+
const missing = (0, full_put_js_1.missingFields)(body, required);
|
|
51
|
+
if (missing.length > 0) {
|
|
52
|
+
throw new Error(`Missing required fields for type ${String(body.type)}: ${missing.join(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
if ((body.type === "A_RECORD" || body.type === "AAAA_RECORD") &&
|
|
55
|
+
typeof body.ttlSeconds === "number" &&
|
|
56
|
+
body.ttlSeconds > 86400) {
|
|
57
|
+
throw new Error(`ttlSeconds for ${body.type} must be 0-86400 (604800 is the CNAME_RECORD maximum)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const uint16 = (description) => zod_1.z.number().int().min(0).max(65535).optional().describe(description);
|
|
61
|
+
const hostname = (description) => zod_1.z.string().min(1).max(127).optional().describe(description);
|
|
62
|
+
const dnsFields = {
|
|
63
|
+
ipv4Address: zod_1.z.string().optional().describe("IPv4 address. A_RECORD."),
|
|
64
|
+
ipv6Address: zod_1.z.string().optional().describe("IPv6 address. AAAA_RECORD."),
|
|
65
|
+
targetDomain: hostname("Canonical name the alias points at. CNAME_RECORD."),
|
|
66
|
+
mailServerDomain: hostname("Mail server domain. MX_RECORD."),
|
|
67
|
+
text: zod_1.z.string().min(1).max(1024).optional().describe("TXT value: up to four 255-character strings; lines containing commas must be double-quoted. TXT_RECORD."),
|
|
68
|
+
serverDomain: hostname("Domain of the server running the service. SRV_RECORD."),
|
|
69
|
+
service: zod_1.z.string().optional().describe("Service, e.g. _ldap. SRV_RECORD."),
|
|
70
|
+
protocol: zod_1.z.string().optional().describe("Protocol, e.g. _tcp. SRV_RECORD."),
|
|
71
|
+
port: uint16("Service port. SRV_RECORD."),
|
|
72
|
+
priority: uint16("Priority; lower is preferred. MX_RECORD, SRV_RECORD."),
|
|
73
|
+
weight: uint16("Relative weight among records with the same priority. SRV_RECORD."),
|
|
74
|
+
ipAddress: zod_1.z.string().optional().describe("IP address of the DNS server queries are forwarded to. FORWARD_DOMAIN."),
|
|
75
|
+
ttlSeconds: zod_1.z
|
|
76
|
+
.number()
|
|
77
|
+
.int()
|
|
78
|
+
.min(0)
|
|
79
|
+
.max(604800)
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Time to live in seconds: 0-86400 for A/AAAA_RECORD, 0-604800 for CNAME_RECORD"),
|
|
82
|
+
};
|
|
9
83
|
function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
10
84
|
server.registerTool("unifi_list_dns_policies", {
|
|
11
|
-
description: "List DNS policies (local DNS records and forward rules served by the gateway) at a site. Returns: id, type (A_RECORD, AAAA_RECORD, CNAME_RECORD, MX_RECORD, TXT_RECORD, SRV_RECORD, FORWARD_DOMAIN), enabled, domain,
|
|
85
|
+
description: "List DNS policies (local DNS records and forward rules served by the gateway) at a site. Returns: id, type (A_RECORD, AAAA_RECORD, CNAME_RECORD, MX_RECORD, TXT_RECORD, SRV_RECORD, FORWARD_DOMAIN), enabled, domain, metadata.origin, plus type-specific fields: A ipv4Address+ttlSeconds; AAAA ipv6Address+ttlSeconds; CNAME targetDomain+ttlSeconds; MX mailServerDomain+priority; TXT text; SRV serverDomain/service/protocol/port/priority/weight; FORWARD_DOMAIN ipAddress (per 11.0.83 response samples).",
|
|
12
86
|
inputSchema: {
|
|
13
|
-
siteId:
|
|
87
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
14
88
|
offset: zod_1.z
|
|
15
89
|
.number()
|
|
16
90
|
.int()
|
|
17
91
|
.nonnegative()
|
|
18
92
|
.optional()
|
|
19
|
-
.describe("Number of records to skip
|
|
93
|
+
.describe("Number of records to skip"),
|
|
20
94
|
limit: zod_1.z
|
|
21
95
|
.number()
|
|
22
96
|
.int()
|
|
23
97
|
.min(0)
|
|
24
98
|
.max(200)
|
|
25
99
|
.optional()
|
|
26
|
-
.describe("Number of records to return (default: 25
|
|
100
|
+
.describe("Number of records to return (API default: 25)"),
|
|
27
101
|
filter: zod_1.z
|
|
28
102
|
.string()
|
|
29
103
|
.optional()
|
|
30
|
-
.describe("Filter expression"),
|
|
104
|
+
.describe("Filter expression, e.g. type.eq('A_RECORD')"),
|
|
31
105
|
},
|
|
32
106
|
outputSchema: output_schemas_js_1.listDnsPoliciesOutputSchema,
|
|
33
107
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -44,8 +118,8 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
44
118
|
server.registerTool("unifi_get_dns_policy", {
|
|
45
119
|
description: "Get a specific DNS policy by ID (same fields as the list entry).",
|
|
46
120
|
inputSchema: {
|
|
47
|
-
siteId:
|
|
48
|
-
dnsPolicyId:
|
|
121
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
122
|
+
dnsPolicyId: (0, params_js_1.resourceId)().describe("DNS policy ID"),
|
|
49
123
|
},
|
|
50
124
|
outputSchema: output_schemas_js_1.dnsPolicyOutputSchema,
|
|
51
125
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -61,39 +135,24 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
61
135
|
if (readOnly)
|
|
62
136
|
return;
|
|
63
137
|
server.registerTool("unifi_create_dns_policy", {
|
|
64
|
-
description:
|
|
138
|
+
description: `Create a new DNS policy. Every type takes type, enabled, domain; the rest depends on type (per 11.0.83 docs): ${DNS_TYPES_DOC} Fields belonging to other types are rejected. Idempotency: not safe to retry — re-running creates a duplicate record.`,
|
|
65
139
|
inputSchema: {
|
|
66
|
-
siteId:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"A_RECORD",
|
|
70
|
-
"AAAA_RECORD",
|
|
71
|
-
"CNAME_RECORD",
|
|
72
|
-
"MX_RECORD",
|
|
73
|
-
"TXT_RECORD",
|
|
74
|
-
"SRV_RECORD",
|
|
75
|
-
"FORWARD_DOMAIN",
|
|
76
|
-
])
|
|
77
|
-
.describe("DNS record type"),
|
|
140
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
141
|
+
...dnsFields,
|
|
142
|
+
type: dnsType,
|
|
78
143
|
enabled: zod_1.z.boolean().describe("Whether the DNS policy is enabled"),
|
|
79
144
|
domain: zod_1.z.string().min(1).max(127).describe("Domain name"),
|
|
80
|
-
ipv4Address: zod_1.z.string().describe("IPv4 address for the record"),
|
|
81
|
-
ttlSeconds: zod_1.z
|
|
82
|
-
.number()
|
|
83
|
-
.int()
|
|
84
|
-
.min(0)
|
|
85
|
-
.max(86400)
|
|
86
|
-
.describe("Time to live in seconds"),
|
|
87
145
|
dryRun: zod_1.z
|
|
88
146
|
.boolean()
|
|
89
147
|
.optional()
|
|
90
148
|
.describe("Preview this action without executing it"),
|
|
91
149
|
},
|
|
92
|
-
outputSchema: output_schemas_js_1.dnsPolicyOutputSchema,
|
|
150
|
+
outputSchema: (0, output_schemas_js_1.writeResult)(output_schemas_js_1.dnsPolicyOutputSchema),
|
|
93
151
|
annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
|
|
94
|
-
}, async ({ siteId,
|
|
152
|
+
}, async ({ siteId, dryRun, ...fields }) => {
|
|
95
153
|
try {
|
|
96
|
-
const body =
|
|
154
|
+
const body = (0, full_put_js_1.definedEntries)(fields);
|
|
155
|
+
checkDnsBody(body, body);
|
|
97
156
|
if (dryRun)
|
|
98
157
|
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/dns/policies`, body);
|
|
99
158
|
const data = await client.post(`/sites/${siteId}/dns/policies`, body);
|
|
@@ -104,44 +163,35 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
104
163
|
}
|
|
105
164
|
});
|
|
106
165
|
server.registerTool("unifi_update_dns_policy", {
|
|
107
|
-
description:
|
|
166
|
+
description: `Update a DNS policy. Pass only the fields to change. The API's PUT replaces the whole policy, so this tool reads the current policy, keeps the documented fields for the (possibly new) type, overlays your changes and sends the full body. Changing type drops the old type's fields, so supply the new type's required fields: ${DNS_TYPES_DOC} dryRun shows the merged body.`,
|
|
108
167
|
inputSchema: {
|
|
109
|
-
siteId:
|
|
110
|
-
dnsPolicyId:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"CNAME_RECORD",
|
|
116
|
-
"MX_RECORD",
|
|
117
|
-
"TXT_RECORD",
|
|
118
|
-
"SRV_RECORD",
|
|
119
|
-
"FORWARD_DOMAIN",
|
|
120
|
-
])
|
|
121
|
-
.describe("DNS record type"),
|
|
122
|
-
enabled: zod_1.z.boolean().describe("Whether the DNS policy is enabled"),
|
|
123
|
-
domain: zod_1.z.string().min(1).max(127).describe("Domain name"),
|
|
124
|
-
ipv4Address: zod_1.z.string().describe("IPv4 address for the record"),
|
|
125
|
-
ttlSeconds: zod_1.z
|
|
126
|
-
.number()
|
|
127
|
-
.int()
|
|
128
|
-
.min(0)
|
|
129
|
-
.max(86400)
|
|
130
|
-
.describe("Time to live in seconds"),
|
|
168
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
169
|
+
dnsPolicyId: (0, params_js_1.resourceId)().describe("DNS policy ID"),
|
|
170
|
+
...dnsFields,
|
|
171
|
+
type: dnsType.optional(),
|
|
172
|
+
enabled: zod_1.z.boolean().optional().describe("Whether the DNS policy is enabled"),
|
|
173
|
+
domain: zod_1.z.string().min(1).max(127).optional().describe("Domain name"),
|
|
131
174
|
dryRun: zod_1.z
|
|
132
175
|
.boolean()
|
|
133
176
|
.optional()
|
|
134
|
-
.describe("Preview
|
|
177
|
+
.describe("Preview the merged PUT body without executing it (still reads the current policy)"),
|
|
135
178
|
},
|
|
136
|
-
outputSchema: output_schemas_js_1.dnsPolicyOutputSchema,
|
|
179
|
+
outputSchema: (0, output_schemas_js_1.writeResult)(output_schemas_js_1.dnsPolicyOutputSchema),
|
|
137
180
|
annotations: safety_js_1.WRITE,
|
|
138
|
-
}, async ({ siteId, dnsPolicyId,
|
|
181
|
+
}, async ({ siteId, dnsPolicyId, dryRun, ...fields }) => {
|
|
139
182
|
try {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
183
|
+
const path = `/sites/${siteId}/dns/policies/${dnsPolicyId}`;
|
|
184
|
+
return await (0, full_put_js_1.withResourceLock)(path, async () => {
|
|
185
|
+
const current = (0, full_put_js_1.asObject)(await client.get(path), "the current DNS policy");
|
|
186
|
+
const updates = (0, full_put_js_1.definedEntries)(fields);
|
|
187
|
+
const { allowed } = fieldsForDnsType(updates.type ?? current.type);
|
|
188
|
+
const body = (0, full_put_js_1.mergeForPut)(current, updates, allowed);
|
|
189
|
+
checkDnsBody(body, updates);
|
|
190
|
+
if (dryRun)
|
|
191
|
+
return (0, safety_js_1.formatDryRun)("PUT", path, body);
|
|
192
|
+
const data = await client.put(path, body);
|
|
193
|
+
return (0, responses_js_1.formatSuccess)(data, { structured: true });
|
|
194
|
+
});
|
|
145
195
|
}
|
|
146
196
|
catch (err) {
|
|
147
197
|
return (0, responses_js_1.formatError)(err);
|
|
@@ -150,12 +200,9 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
150
200
|
server.registerTool("unifi_delete_dns_policy", {
|
|
151
201
|
description: "DESTRUCTIVE: Delete a DNS policy",
|
|
152
202
|
inputSchema: {
|
|
153
|
-
siteId:
|
|
154
|
-
dnsPolicyId:
|
|
155
|
-
confirm:
|
|
156
|
-
.boolean()
|
|
157
|
-
.optional()
|
|
158
|
-
.describe("Must be true to execute this destructive action"),
|
|
203
|
+
siteId: (0, params_js_1.resourceId)().describe("Site ID"),
|
|
204
|
+
dnsPolicyId: (0, params_js_1.resourceId)().describe("DNS policy ID"),
|
|
205
|
+
confirm: (0, safety_js_1.confirmDestructive)(),
|
|
159
206
|
dryRun: zod_1.z
|
|
160
207
|
.boolean()
|
|
161
208
|
.optional()
|
|
@@ -163,12 +210,12 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
163
210
|
},
|
|
164
211
|
annotations: safety_js_1.DESTRUCTIVE,
|
|
165
212
|
}, async ({ siteId, dnsPolicyId, confirm, dryRun }) => {
|
|
166
|
-
const guard = (0, safety_js_1.requireConfirmation)(confirm, "This will delete the DNS policy");
|
|
213
|
+
const guard = dryRun ? null : (0, safety_js_1.requireConfirmation)(confirm, "This will delete the DNS policy");
|
|
167
214
|
if (guard)
|
|
168
215
|
return guard;
|
|
169
216
|
try {
|
|
170
217
|
if (dryRun)
|
|
171
|
-
return (0, safety_js_1.formatDryRun)("DELETE", `/sites/${siteId}/dns/policies/${dnsPolicyId}
|
|
218
|
+
return (0, safety_js_1.formatDryRun)("DELETE", `/sites/${siteId}/dns/policies/${dnsPolicyId}`);
|
|
172
219
|
const data = await client.delete(`/sites/${siteId}/dns/policies/${dnsPolicyId}`);
|
|
173
220
|
return (0, responses_js_1.formatSuccess)(data);
|
|
174
221
|
}
|
package/dist/tools/firewall.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { NetworkClient } from "../client.js";
|
|
3
|
-
|
|
3
|
+
import { type WanPolicy } from "../utils/wan-exposure.js";
|
|
4
|
+
export declare function registerFirewallTools(server: McpServer, client: NetworkClient, readOnly?: boolean, wan?: WanPolicy): void;
|