@owine/unifi-network-mcp 2.12.0 → 2.14.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/README.md +30 -4
- package/dist/client.d.ts +11 -0
- package/dist/client.js +90 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +7 -0
- package/dist/tools/acl.js +2 -2
- package/dist/tools/client-history.d.ts +3 -0
- package/dist/tools/client-history.js +61 -0
- package/dist/tools/clients.js +4 -4
- package/dist/tools/devices.js +8 -6
- package/dist/tools/dns-policies.js +2 -2
- package/dist/tools/firewall.js +3 -3
- package/dist/tools/hotspot.js +4 -4
- package/dist/tools/index.js +2 -0
- package/dist/tools/networks.js +3 -3
- package/dist/tools/sites.js +2 -2
- package/dist/tools/supporting.js +11 -11
- package/dist/tools/switching.js +5 -5
- package/dist/tools/system.js +1 -1
- package/dist/tools/traffic-matching.js +1 -1
- package/dist/tools/wifi.js +6 -6
- package/dist/utils/output-schemas.d.ts +79 -1
- package/dist/utils/output-schemas.js +62 -5
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# UniFi Network MCP Server
|
|
2
2
|
|
|
3
|
-
An MCP (Model Context Protocol) server that exposes the UniFi Network Integration API as tools for Claude Code and other MCP clients. Provides
|
|
3
|
+
An MCP (Model Context Protocol) server that exposes the UniFi Network Integration API as tools for Claude Code and other MCP clients. Provides 76 tools for managing sites, devices, clients, networks, WiFi, firewalls, ACLs, switching, DNS policies, hotspot vouchers, VPNs, and more.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
@@ -79,11 +79,11 @@ This server provides layered safety controls for responsible operation:
|
|
|
79
79
|
|
|
80
80
|
## Structured Output
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
60 of the 76 tools (all 43 read tools, plus the 17 write tools whose API responses return the affected resource) declare an MCP `outputSchema` and return `structuredContent` alongside the usual text content. Clients that understand structured output get typed, machine-readable results instead of parsing JSON out of a text blob.
|
|
83
83
|
|
|
84
|
-
The schemas live in `src/utils/output-schemas.ts` and are verified against UniFi Network API 10.
|
|
84
|
+
The schemas live in `src/utils/output-schemas.ts` and are verified against UniFi Network API 10.6.106. They deliberately use a **loose strategy**: every non-key field is optional and nested objects use `.passthrough()`, so firmware- and hardware-specific fields flow through unchanged rather than being stripped or triggering a validation error. This keeps the contract stable across console versions and hardware models.
|
|
85
85
|
|
|
86
|
-
## Tools (
|
|
86
|
+
## Tools (76 total)
|
|
87
87
|
|
|
88
88
|
### System (1)
|
|
89
89
|
| Tool | Description |
|
|
@@ -115,6 +115,12 @@ The schemas live in `src/utils/output-schemas.ts` and are verified against UniFi
|
|
|
115
115
|
| `unifi_authorize_guest` | Authorize a guest client on a hotspot network |
|
|
116
116
|
| `unifi_unauthorize_guest` | Unauthorize a guest client |
|
|
117
117
|
|
|
118
|
+
### Client history (2)
|
|
119
|
+
| Tool | Description |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `unifi_list_client_history` | Historical/offline client inventory and first/last seen times |
|
|
122
|
+
| `unifi_list_client_sessions` | Retained connection sessions, duration, byte counters and roaming records |
|
|
123
|
+
|
|
118
124
|
### Networks (6)
|
|
119
125
|
| Tool | Description |
|
|
120
126
|
|---|---|
|
|
@@ -245,3 +251,23 @@ git commit --allow-empty -m "chore: release 2.0.0" -m "Release-As: 2.0.0"
|
|
|
245
251
|
## License
|
|
246
252
|
|
|
247
253
|
MIT
|
|
254
|
+
|
|
255
|
+
## Retained client connections
|
|
256
|
+
|
|
257
|
+
The Integration API only lists connected clients. The two history tools use the controller APIs instead: GET `/proxy/network/v2/api/site/{siteReference}/clients/history` and POST `/proxy/network/api/s/{siteReference}/stat/session`. The POST is a statistics query and does not change the network. Both tools remain available in read-only mode.
|
|
258
|
+
|
|
259
|
+
Use `internalReference` from `unifi_list_sites` (often `default`), not its UUID. Session queries require `start` and `end` in Unix **seconds**, with a maximum 31-day window. The controller ignores `_start`/offset on this endpoint. A full result sets `mayBeTruncated=true`; split the time window or filter by MAC and query again. Overlap split boundaries and deduplicate session IDs. A result below the requested limit means the query was not capped, not that all dates were retained.
|
|
260
|
+
|
|
261
|
+
```json
|
|
262
|
+
{"siteReference":"default","start":1789272000,"end":1789876800,"limit":1000}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Results preserve controller field names, including `assoc_time`, `duration`, `rx_bytes`, `tx_bytes` and `roaming_sessions`. RX/TX are the controller's counters, not relabeled client download/upload. Inventory is separate from session history. Retention varies; a non-truncated result does not prove all requested dates were retained, and ongoing sessions may not be present. Neither association nor traffic counters establish end-to-end Internet success. The `is_guest` flag does not establish device ownership.
|
|
266
|
+
|
|
267
|
+
These controller routes are not part of the published Integration API contract. Their availability and API-key permissions vary by controller version. Unsupported, unauthorized, HTML login, legacy `meta.rc` error and malformed responses fail explicitly instead of becoming an empty history. History requests have a 25-second timeout, reject redirects, and cap responses at 10 MB. No browser cookies are required where API-key access is supported.
|
|
268
|
+
|
|
269
|
+
### Cloud Connector
|
|
270
|
+
|
|
271
|
+
To use a Site Manager API key with a console, set `UNIFI_NETWORK_HOST=api.ui.com` and `UNIFI_NETWORK_CONSOLE_ID` to its console ID. The existing `UNIFI_NETWORK_API_KEY` supplies the key. This works for current Integration API tools and the controller history routes when permitted by the console/key. Cloud Connector requires eligible console ownership/access and firmware 5.0.3 or later. Keep TLS verification enabled.
|
|
272
|
+
|
|
273
|
+
References: [official Cloud Connector API](https://developer.ui.com/site-manager/v1.0.0) and the [Art-of-WiFi controller client](https://github.com/Art-of-WiFi/UniFi-API-client/blob/master/src/Client.php), whose session queries use epoch seconds.
|
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Config } from "./config.js";
|
|
|
2
2
|
export declare class NetworkClient {
|
|
3
3
|
private baseUrl;
|
|
4
4
|
private headers;
|
|
5
|
+
private networkUrl;
|
|
5
6
|
constructor(config: Config);
|
|
6
7
|
private request;
|
|
7
8
|
get(path: string): Promise<unknown>;
|
|
@@ -9,4 +10,14 @@ export declare class NetworkClient {
|
|
|
9
10
|
put(path: string, body: unknown): Promise<unknown>;
|
|
10
11
|
patch(path: string, body: unknown): Promise<unknown>;
|
|
11
12
|
delete(path: string): Promise<unknown>;
|
|
13
|
+
/** These controller endpoints are read queries, including stat/session's POST. */
|
|
14
|
+
getClientHistory(siteReference: string, withinHours: number): Promise<unknown>;
|
|
15
|
+
getClientSessions(siteReference: string, query: {
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
limit: number;
|
|
19
|
+
mac?: string;
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
private validateSiteReference;
|
|
22
|
+
private historyRequest;
|
|
12
23
|
}
|
package/dist/client.js
CHANGED
|
@@ -4,8 +4,18 @@ exports.NetworkClient = void 0;
|
|
|
4
4
|
class NetworkClient {
|
|
5
5
|
baseUrl;
|
|
6
6
|
headers;
|
|
7
|
+
networkUrl;
|
|
7
8
|
constructor(config) {
|
|
8
|
-
|
|
9
|
+
// Cloud Connector only exists behind api.ui.com. A console ID set against a
|
|
10
|
+
// local controller is ignored rather than fatal: the other tools work fine
|
|
11
|
+
// against that controller, and taking the whole server down helps nobody.
|
|
12
|
+
const useConnector = config.consoleId !== undefined && config.host === "api.ui.com";
|
|
13
|
+
if (config.consoleId !== undefined && !useConnector) {
|
|
14
|
+
console.error(`Ignoring UNIFI_NETWORK_CONSOLE_ID: Cloud Connector requires host api.ui.com, but host is ${config.host}.`);
|
|
15
|
+
}
|
|
16
|
+
const consolePath = useConnector ? `/v1/connector/consoles/${config.consoleId}` : "";
|
|
17
|
+
this.networkUrl = `https://${config.host}${consolePath}/proxy/network`;
|
|
18
|
+
this.baseUrl = `${this.networkUrl}/integration/v1`;
|
|
9
19
|
this.headers = {
|
|
10
20
|
"X-API-KEY": config.apiKey,
|
|
11
21
|
"Content-Type": "application/json",
|
|
@@ -49,5 +59,84 @@ class NetworkClient {
|
|
|
49
59
|
async delete(path) {
|
|
50
60
|
return this.request("DELETE", path);
|
|
51
61
|
}
|
|
62
|
+
/** These controller endpoints are read queries, including stat/session's POST. */
|
|
63
|
+
async getClientHistory(siteReference, withinHours) {
|
|
64
|
+
this.validateSiteReference(siteReference);
|
|
65
|
+
if (!Number.isInteger(withinHours) || withinHours < 1 || withinHours > 8760) {
|
|
66
|
+
throw new Error("withinHours must be an integer from 1 to 8760");
|
|
67
|
+
}
|
|
68
|
+
return this.historyRequest(`/v2/api/site/${siteReference}/clients/history?withinHours=${withinHours}`);
|
|
69
|
+
}
|
|
70
|
+
async getClientSessions(siteReference, query) {
|
|
71
|
+
this.validateSiteReference(siteReference);
|
|
72
|
+
const { start, end, limit, mac } = query;
|
|
73
|
+
if (![start, end, limit].every(Number.isSafeInteger) || start < 0 || end > 4102444800 ||
|
|
74
|
+
end <= start || end - start > 31 * 86400 || limit < 1 || limit > 1000 ||
|
|
75
|
+
(mac !== undefined && !/^([\da-f]{2}:){5}[\da-f]{2}$/i.test(mac))) {
|
|
76
|
+
throw new Error("Invalid session query: use epoch seconds, an increasing window of at most 31 days, a valid limit and MAC");
|
|
77
|
+
}
|
|
78
|
+
return this.historyRequest(`/api/s/${siteReference}/stat/session`, {
|
|
79
|
+
type: "all", start, end, _limit: limit, _sort: "-assoc_time",
|
|
80
|
+
...(mac ? { mac: mac.toLowerCase() } : {}),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
validateSiteReference(siteReference) {
|
|
84
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(siteReference)) {
|
|
85
|
+
throw new Error("Use the site's internalReference from unifi_list_sites (for example default)");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async historyRequest(path, body) {
|
|
89
|
+
const response = await fetch(`${this.networkUrl}${path}`, {
|
|
90
|
+
method: body === undefined ? "GET" : "POST", headers: this.headers,
|
|
91
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
92
|
+
redirect: "error", signal: AbortSignal.timeout(25000),
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
// Drain before throwing: an unread body pins the socket until GC, so a
|
|
96
|
+
// run of failures (say, a mis-scoped API key) would leak connections.
|
|
97
|
+
await response.body?.cancel();
|
|
98
|
+
throw new Error(`Controller history returned HTTP ${response.status}; verify API-key access and controller support. This is not an empty history result.`);
|
|
99
|
+
}
|
|
100
|
+
if (!response.headers.get("content-type")?.includes("application/json")) {
|
|
101
|
+
await response.body?.cancel();
|
|
102
|
+
throw new Error("Controller history returned non-JSON data; verify controller authentication");
|
|
103
|
+
}
|
|
104
|
+
// Bound the body before parsing: controller session records can be large.
|
|
105
|
+
const reader = response.body?.getReader();
|
|
106
|
+
if (!reader)
|
|
107
|
+
throw new Error("Controller history returned no response body");
|
|
108
|
+
const chunks = [];
|
|
109
|
+
let bytes = 0;
|
|
110
|
+
try {
|
|
111
|
+
while (true) {
|
|
112
|
+
const { done, value } = await reader.read();
|
|
113
|
+
if (done)
|
|
114
|
+
break;
|
|
115
|
+
bytes += value.length;
|
|
116
|
+
if (bytes > 10000000) {
|
|
117
|
+
await reader.cancel();
|
|
118
|
+
throw new Error("Controller history exceeds 10 MB; reduce the page size or time window");
|
|
119
|
+
}
|
|
120
|
+
chunks.push(value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
reader.releaseLock();
|
|
125
|
+
}
|
|
126
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
127
|
+
if (text.trim() === "") {
|
|
128
|
+
// A bare JSON.parse would surface "Unexpected end of JSON input", which
|
|
129
|
+
// reads as a broken tool rather than an unusable controller response.
|
|
130
|
+
throw new Error("Controller history returned an empty body; verify controller support. This is not an empty history result.");
|
|
131
|
+
}
|
|
132
|
+
const result = JSON.parse(text);
|
|
133
|
+
if (result && typeof result === "object" && "meta" in result) {
|
|
134
|
+
const meta = result.meta;
|
|
135
|
+
if (meta && typeof meta === "object" && "rc" in meta && meta.rc !== "ok") {
|
|
136
|
+
throw new Error("Controller rejected the history query (meta.rc is not ok)");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
52
141
|
}
|
|
53
142
|
exports.NetworkClient = NetworkClient;
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -5,6 +5,11 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const configSchema = zod_1.z.object({
|
|
6
6
|
host: zod_1.z.string().min(1).describe("UniFi Network host (IP or hostname)"),
|
|
7
7
|
apiKey: zod_1.z.string().min(1).describe("UniFi Network API key"),
|
|
8
|
+
// Console/host IDs are hex, UUID- or colon-shaped depending on vintage, so
|
|
9
|
+
// hyphens and underscores are allowed; the charset still excludes anything
|
|
10
|
+
// that could escape the URL path segment it is interpolated into.
|
|
11
|
+
consoleId: zod_1.z.string().regex(/^[A-Za-z0-9:_-]{1,128}$/).optional()
|
|
12
|
+
.describe("Optional Cloud Connector console ID; only used when host is api.ui.com"),
|
|
8
13
|
verifySsl: zod_1.z
|
|
9
14
|
.boolean()
|
|
10
15
|
.default(true)
|
|
@@ -18,6 +23,8 @@ function loadConfig() {
|
|
|
18
23
|
const result = configSchema.safeParse({
|
|
19
24
|
host: process.env.UNIFI_NETWORK_HOST,
|
|
20
25
|
apiKey: process.env.UNIFI_NETWORK_API_KEY,
|
|
26
|
+
...(process.env.UNIFI_NETWORK_CONSOLE_ID
|
|
27
|
+
? { consoleId: process.env.UNIFI_NETWORK_CONSOLE_ID } : {}),
|
|
21
28
|
verifySsl: process.env.UNIFI_NETWORK_VERIFY_SSL?.toLowerCase() !== "false",
|
|
22
29
|
readOnly: process.env.UNIFI_NETWORK_READ_ONLY?.toLowerCase() !== "false",
|
|
23
30
|
});
|
package/dist/tools/acl.js
CHANGED
|
@@ -8,7 +8,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
9
|
function registerAclTools(server, client, readOnly = false) {
|
|
10
10
|
server.registerTool("unifi_list_acl_rules", {
|
|
11
|
-
description: "List ACL rules (switch/AP-level access control lists, distinct from zone-based firewall policies) at a site. Returns: id, type (IPV4/MAC), name, enabled, action (ALLOW/BLOCK), description, protocolFilter,
|
|
11
|
+
description: "List ACL rules (switch/AP-level access control lists, distinct from zone-based firewall policies) at a site. Returns: id, type (IPV4/MAC), name, enabled, action (ALLOW/BLOCK), description, protocolFilter, sourceFilter/destinationFilter matchers, enforcingDeviceFilter (which devices apply the rule), index (evaluation order — read-only; change it with unifi_reorder_acl_rules), metadata.origin. ACLs apply earlier in the path than firewall policies.",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
14
14
|
offset: zod_1.z
|
|
@@ -20,7 +20,7 @@ function registerAclTools(server, client, readOnly = false) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerClientHistoryTools = registerClientHistoryTools;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const responses_js_1 = require("../utils/responses.js");
|
|
6
|
+
const safety_js_1 = require("../utils/safety.js");
|
|
7
|
+
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
8
|
+
const siteReference = zod_1.z.string().regex(/^[A-Za-z0-9_-]{1,64}$/)
|
|
9
|
+
.describe("Site internalReference from unifi_list_sites (often default), not the Integration API UUID");
|
|
10
|
+
function registerClientHistoryTools(server, client) {
|
|
11
|
+
server.registerTool("unifi_list_client_history", {
|
|
12
|
+
description: "List historical/offline client inventory using the controller v2 API. Includes names, MACs, first_seen and last_seen epoch seconds when available. This is inventory, not session history; use unifi_list_client_sessions for past connections. Requires controller history API-key support; errors never mean zero activity. Retention depends on the controller.",
|
|
13
|
+
inputSchema: { siteReference, withinHours: zod_1.z.number().int().min(1).max(8760).default(168).describe("Lookback in hours, up to one year; does not extend controller retention") },
|
|
14
|
+
outputSchema: output_schemas_js_1.listClientHistoryOutputSchema,
|
|
15
|
+
annotations: safety_js_1.READ_ONLY,
|
|
16
|
+
}, async ({ siteReference, withinHours }) => {
|
|
17
|
+
try {
|
|
18
|
+
const data = zod_1.z.array(output_schemas_js_1.HistoryClient).parse(await client.getClientHistory(siteReference, withinHours));
|
|
19
|
+
return (0, responses_js_1.formatSuccess)({ data, count: data.length, withinHours, coverage: "Historical client inventory returned by the controller; not a complete session log." }, { structured: true });
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return (0, responses_js_1.formatError)(err);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
server.registerTool("unifi_list_client_sessions", {
|
|
26
|
+
description: "Read retained past client connections, including disconnected clients, from the controller stat/session query endpoint. POST here only reads statistics. Returns controller session IDs, MACs, assoc_time (epoch seconds), duration (seconds), rx_bytes/tx_bytes (controller perspective), and roaming_sessions when available. Use an explicit UTC epoch-second window (max 31 days). If mayBeTruncated is true, split the time window (overlap boundaries and deduplicate IDs) or filter by MAC. The controller ignores offset pagination. Records reflect controller retention and may omit ongoing sessions. Wi-Fi association/bytes do not establish end-to-end Internet success. Do not interpret controller is_guest as proof of a person's identity or ownership.",
|
|
27
|
+
inputSchema: {
|
|
28
|
+
siteReference,
|
|
29
|
+
start: zod_1.z.number().int().min(0).max(4102444800).describe("Start Unix epoch seconds, not milliseconds"),
|
|
30
|
+
end: zod_1.z.number().int().min(1).max(4102444800).describe("End Unix epoch seconds; after start and at most 31 days later"),
|
|
31
|
+
mac: zod_1.z.string().regex(/^([\da-f]{2}:){5}[\da-f]{2}$/i).optional().describe("Optional client MAC address"),
|
|
32
|
+
limit: zod_1.z.number().int().min(1).max(1000).default(1000).describe("Maximum records; a full result requires a narrower window or MAC filter"),
|
|
33
|
+
},
|
|
34
|
+
outputSchema: output_schemas_js_1.listClientSessionsOutputSchema,
|
|
35
|
+
annotations: safety_js_1.READ_ONLY,
|
|
36
|
+
}, async ({ siteReference, start, end, limit, mac }) => {
|
|
37
|
+
try {
|
|
38
|
+
if (end <= start || end - start > 31 * 86400)
|
|
39
|
+
throw new Error("Use an increasing time window of at most 31 days");
|
|
40
|
+
const { data: rows } = output_schemas_js_1.ClientSessionEnvelope.parse(await client.getClientSessions(siteReference, { start, end, limit, mac }));
|
|
41
|
+
// The controller honours neither _start nor _limit reliably, and can
|
|
42
|
+
// repeat a session ID across roaming records. Normalise instead of
|
|
43
|
+
// rejecting: a usable page is better than an error with no data.
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const deduped = [];
|
|
46
|
+
for (const row of rows) {
|
|
47
|
+
if (seen.has(row._id))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(row._id);
|
|
50
|
+
deduped.push(row);
|
|
51
|
+
}
|
|
52
|
+
const data = deduped.slice(0, limit);
|
|
53
|
+
const mayBeTruncated = deduped.length >= limit;
|
|
54
|
+
return (0, responses_js_1.formatSuccess)({ data, count: data.length, limit, mayBeTruncated, start, end,
|
|
55
|
+
coverage: "Retained controller sessions; A non-truncated response does not prove retention coverage or include every ongoing connection." }, { structured: true });
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
return (0, responses_js_1.formatError)(err);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
package/dist/tools/clients.js
CHANGED
|
@@ -8,7 +8,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
9
|
function registerClientTools(server, client, readOnly = false) {
|
|
10
10
|
server.registerTool("unifi_list_clients", {
|
|
11
|
-
description: "List currently connected clients at a site. Returns per client: id, name, type (WIRED/WIRELESS/VPN/TELEPORT), macAddress, ipAddress, connectedAt, uplinkDeviceId (the switch/AP they're attached to), access.type. NOTE: verified against 10.
|
|
11
|
+
description: "List currently connected clients at a site. Returns per client: id, name, type (WIRED/WIRELESS/VPN/TELEPORT), macAddress, ipAddress (frequently absent — do not rely on it being present), connectedAt, uplinkDeviceId (the switch/AP they're attached to), access.type. NOTE: verified against 10.6.106 — the Integration API client schema is minimal and identical across types; it does NOT expose signal strength, channel, or per-port binding. Use for: who's online right now. Disconnected/historical clients are NOT in the Integration API; use unifi_list_client_history and unifi_list_client_sessions for controller-retained records.",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
14
14
|
offset: zod_1.z
|
|
@@ -20,7 +20,7 @@ function registerClientTools(server, client, readOnly = false) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -58,7 +58,7 @@ function registerClientTools(server, client, readOnly = false) {
|
|
|
58
58
|
if (readOnly)
|
|
59
59
|
return;
|
|
60
60
|
server.registerTool("unifi_authorize_guest", {
|
|
61
|
-
description: "Authorize a guest client through a captive-portal hotspot. Optional limits override hotspot defaults. Idempotency: not safe to retry — re-authorizing extends the session.",
|
|
61
|
+
description: "Authorize a guest client through a captive-portal hotspot. Optional limits override hotspot defaults. Returns (per 10.6.106 docs, not live-verified): action, grantedAuthorization and revokedAuthorization objects (authorizedAt, authorizationMethod, expiresAt, dataUsageLimitMBytes, rxRateLimitKbps, txRateLimitKbps, usage). Idempotency: not safe to retry — re-authorizing extends the session.",
|
|
62
62
|
inputSchema: {
|
|
63
63
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
64
64
|
clientId: zod_1.z.string().describe("Client ID"),
|
|
@@ -116,7 +116,7 @@ function registerClientTools(server, client, readOnly = false) {
|
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
server.registerTool("unifi_unauthorize_guest", {
|
|
119
|
-
description: "Revoke guest authorization; the client returns to the captive portal on next request. Idempotent.",
|
|
119
|
+
description: "Revoke guest authorization; the client returns to the captive portal on next request. Returns (per 10.6.106 docs, not live-verified): action plus a revokedAuthorization object. Idempotent.",
|
|
120
120
|
inputSchema: {
|
|
121
121
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
122
122
|
clientId: zod_1.z.string().describe("Client ID"),
|
package/dist/tools/devices.js
CHANGED
|
@@ -22,7 +22,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
22
22
|
limit: zod_1.z
|
|
23
23
|
.number()
|
|
24
24
|
.int()
|
|
25
|
-
.min(
|
|
25
|
+
.min(0)
|
|
26
26
|
.max(200)
|
|
27
27
|
.optional()
|
|
28
28
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -41,7 +41,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
server.registerTool("unifi_get_device", {
|
|
44
|
-
description: "Get full configuration for a device. Returns (in addition to list fields): supported, firmwareUpdatable, provisionedAt, configurationId, uplink.deviceId, features (object keyed by capability: switching {lags[]} / accessPoint {}), interfaces.ports[] for switches ({idx, state, connector, maxSpeedMbps, speedMbps, poe:{standard, type, enabled, state}}), interfaces.radios[] for APs ({wlanStandard, frequencyGHz, channelWidthMHz, channel}). 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.",
|
|
44
|
+
description: "Get full configuration for a device. Returns (in addition to list fields): supported, firmwareUpdatable, adoptedAt, provisionedAt, configurationId, uplink.deviceId, features (object keyed by capability: switching {lags[]} / accessPoint {}), interfaces.ports[] for switches ({idx, state, connector, maxSpeedMbps, speedMbps, poe:{standard, type, enabled, state}}), interfaces.radios[] for APs ({wlanStandard, frequencyGHz, channelWidthMHz, channel}). 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
45
|
inputSchema: {
|
|
46
46
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
47
47
|
deviceId: zod_1.z.string().describe("Device ID"),
|
|
@@ -58,7 +58,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
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 10.
|
|
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 10.6.106 — 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
62
|
inputSchema: {
|
|
63
63
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
64
64
|
deviceId: zod_1.z.string().describe("Device ID"),
|
|
@@ -75,7 +75,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
75
75
|
}
|
|
76
76
|
});
|
|
77
77
|
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 10.
|
|
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 10.6.106 docs). Use for: discovering new devices on the network before calling unifi_adopt_device.",
|
|
79
79
|
inputSchema: {
|
|
80
80
|
offset: zod_1.z
|
|
81
81
|
.number()
|
|
@@ -86,7 +86,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
86
86
|
limit: zod_1.z
|
|
87
87
|
.number()
|
|
88
88
|
.int()
|
|
89
|
-
.min(
|
|
89
|
+
.min(0)
|
|
90
90
|
.max(200)
|
|
91
91
|
.optional()
|
|
92
92
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -111,7 +111,9 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
111
111
|
inputSchema: {
|
|
112
112
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
113
113
|
macAddress: zod_1.z.string().describe("MAC address of the device"),
|
|
114
|
-
|
|
114
|
+
// Required by the API; kept optional here and defaulted to false below
|
|
115
|
+
// so callers need not pass it explicitly.
|
|
116
|
+
ignoreDeviceLimit: zod_1.z.boolean().optional().describe("Ignore device limit when adopting (required by the API; sent as false when omitted)"),
|
|
115
117
|
dryRun: zod_1.z.boolean().optional().describe("Preview this action without executing it"),
|
|
116
118
|
},
|
|
117
119
|
outputSchema: adoptDeviceOutputSchema,
|
|
@@ -8,7 +8,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
9
|
function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
10
10
|
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, ipv4Address, ttlSeconds.",
|
|
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, ipv4Address, ttlSeconds, metadata.origin.",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
14
14
|
offset: zod_1.z
|
|
@@ -20,7 +20,7 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
package/dist/tools/firewall.js
CHANGED
|
@@ -20,7 +20,7 @@ function registerFirewallTools(server, client, readOnly = false) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -71,7 +71,7 @@ function registerFirewallTools(server, client, readOnly = false) {
|
|
|
71
71
|
limit: zod_1.z
|
|
72
72
|
.number()
|
|
73
73
|
.int()
|
|
74
|
-
.min(
|
|
74
|
+
.min(0)
|
|
75
75
|
.max(200)
|
|
76
76
|
.optional()
|
|
77
77
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -270,7 +270,7 @@ function registerFirewallTools(server, client, readOnly = false) {
|
|
|
270
270
|
}
|
|
271
271
|
});
|
|
272
272
|
server.registerTool("unifi_patch_firewall_policy", {
|
|
273
|
-
description: "Partially update a firewall policy without resending all fields.
|
|
273
|
+
description: "Partially update a firewall policy without resending all fields. loggingEnabled is the only field documented as patchable in API 10.6.106; other fields may be rejected — use unifi_update_firewall_policy for a full update. Idempotent for fields supplied.",
|
|
274
274
|
inputSchema: {
|
|
275
275
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
276
276
|
firewallPolicyId: zod_1.z.string().describe("Firewall policy ID"),
|
package/dist/tools/hotspot.js
CHANGED
|
@@ -21,7 +21,7 @@ function registerHotspotTools(server, client, readOnly = false) {
|
|
|
21
21
|
limit: zod_1.z
|
|
22
22
|
.number()
|
|
23
23
|
.int()
|
|
24
|
-
.min(
|
|
24
|
+
.min(0)
|
|
25
25
|
.max(1000)
|
|
26
26
|
.optional()
|
|
27
27
|
.default(100)
|
|
@@ -143,7 +143,7 @@ function registerHotspotTools(server, client, readOnly = false) {
|
|
|
143
143
|
}
|
|
144
144
|
});
|
|
145
145
|
server.registerTool("unifi_delete_voucher", {
|
|
146
|
-
description: "DESTRUCTIVE: Delete a hotspot voucher",
|
|
146
|
+
description: "DESTRUCTIVE: Delete a hotspot voucher. Returns: vouchersDeleted (count of vouchers removed).",
|
|
147
147
|
inputSchema: {
|
|
148
148
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
149
149
|
voucherId: zod_1.z.string().describe("Voucher ID"),
|
|
@@ -173,12 +173,12 @@ function registerHotspotTools(server, client, readOnly = false) {
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
server.registerTool("unifi_bulk_delete_vouchers", {
|
|
176
|
-
description: "DESTRUCTIVE: Bulk delete hotspot vouchers based on filter criteria",
|
|
176
|
+
description: "DESTRUCTIVE: Bulk delete hotspot vouchers based on filter criteria. Returns: vouchersDeleted (count of vouchers removed).",
|
|
177
177
|
inputSchema: {
|
|
178
178
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
179
179
|
filter: zod_1.z
|
|
180
180
|
.string()
|
|
181
|
-
.describe("Required filter expression (e.g., 'expired.eq(true)',
|
|
181
|
+
.describe("Required filter expression (e.g., 'expired.eq(true)', name.like('guest*'))"),
|
|
182
182
|
confirm: zod_1.z
|
|
183
183
|
.boolean()
|
|
184
184
|
.optional()
|
package/dist/tools/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const system_js_1 = require("./system.js");
|
|
|
5
5
|
const sites_js_1 = require("./sites.js");
|
|
6
6
|
const devices_js_1 = require("./devices.js");
|
|
7
7
|
const clients_js_1 = require("./clients.js");
|
|
8
|
+
const client_history_js_1 = require("./client-history.js");
|
|
8
9
|
const networks_js_1 = require("./networks.js");
|
|
9
10
|
const wifi_js_1 = require("./wifi.js");
|
|
10
11
|
const hotspot_js_1 = require("./hotspot.js");
|
|
@@ -19,6 +20,7 @@ function registerAllTools(server, client, readOnly = false) {
|
|
|
19
20
|
(0, sites_js_1.registerSiteTools)(server, client);
|
|
20
21
|
(0, devices_js_1.registerDeviceTools)(server, client, readOnly);
|
|
21
22
|
(0, clients_js_1.registerClientTools)(server, client, readOnly);
|
|
23
|
+
(0, client_history_js_1.registerClientHistoryTools)(server, client);
|
|
22
24
|
(0, networks_js_1.registerNetworkTools)(server, client, readOnly);
|
|
23
25
|
(0, wifi_js_1.registerWifiTools)(server, client, readOnly);
|
|
24
26
|
(0, hotspot_js_1.registerHotspotTools)(server, client, readOnly);
|
package/dist/tools/networks.js
CHANGED
|
@@ -8,7 +8,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
9
|
function registerNetworkTools(server, client, readOnly = false) {
|
|
10
10
|
server.registerTool("unifi_list_networks", {
|
|
11
|
-
description: "List all networks (VLANs/LAN segments) at a site. Returns: id, name, management (UNMANAGED/GATEWAY/SWITCH), enabled, vlanId, default (true for the default network), dhcpGuarding, metadata.origin. NOTE: the list view is sparse — for subnet/DHCP/NTP detail (ipv4Configuration), call unifi_get_network on a specific id. Use for: VLAN inventory; pair with unifi_get_network_references to find what consumes a network.",
|
|
11
|
+
description: "List all networks (VLANs/LAN segments) at a site. Returns: id, name, management (UNMANAGED/GATEWAY/SWITCH), enabled, vlanId, default (true for the default network), zoneId (the firewall zone the network sits in), dhcpGuarding, metadata.origin. NOTE: the list view is sparse — for subnet/DHCP/NTP detail (ipv4Configuration/ipv6Configuration), call unifi_get_network on a specific id. Use for: VLAN inventory; pair with unifi_get_network_references to find what consumes a network.",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
14
14
|
offset: zod_1.z
|
|
@@ -20,7 +20,7 @@ function registerNetworkTools(server, client, readOnly = false) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -42,7 +42,7 @@ function registerNetworkTools(server, client, readOnly = false) {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
server.registerTool("unifi_get_network", {
|
|
45
|
-
description: "Get a network/VLAN by ID. Returns the list fields PLUS (live-verified)
|
|
45
|
+
description: "Get a network/VLAN by ID. Returns the list fields PLUS (live-verified) isolationEnabled, internetAccessEnabled, mdnsForwardingEnabled, cellularBackupEnabled, a full ipv4Configuration object (autoScaleEnabled, hostIpAddress, prefixLength, dhcpConfiguration with mode/ipAddressRange/leaseTimeSeconds/domainName/pingConflictDetectionEnabled/ntpServerIpAddresses), and an ipv6Configuration object (interfaceType, clientAddressAssignment, routerAdvertisement, hostIpAddress, prefixLength). NOTE: subnet/DHCP detail appears here at get-by-id but NOT in unifi_list_networks (sparse list view).",
|
|
46
46
|
inputSchema: {
|
|
47
47
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
48
48
|
networkId: zod_1.z.string().describe("Network ID"),
|
package/dist/tools/sites.js
CHANGED
|
@@ -19,14 +19,14 @@ function registerSiteTools(server, client) {
|
|
|
19
19
|
limit: zod_1.z
|
|
20
20
|
.number()
|
|
21
21
|
.int()
|
|
22
|
-
.min(
|
|
22
|
+
.min(0)
|
|
23
23
|
.max(200)
|
|
24
24
|
.optional()
|
|
25
25
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
26
26
|
filter: zod_1.z
|
|
27
27
|
.string()
|
|
28
28
|
.optional()
|
|
29
|
-
.describe("Filter expression (e.g.,
|
|
29
|
+
.describe("Filter expression (e.g., name.like('office*'))"),
|
|
30
30
|
},
|
|
31
31
|
outputSchema: output_schemas_js_1.listSitesOutputSchema,
|
|
32
32
|
annotations: safety_js_1.READ_ONLY,
|
package/dist/tools/supporting.js
CHANGED
|
@@ -8,7 +8,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
9
|
function registerSupportingTools(server, client) {
|
|
10
10
|
server.registerTool("unifi_list_wans", {
|
|
11
|
-
description: "List WAN interface definitions at a site. Returns: id, name only (verified against 10.
|
|
11
|
+
description: "List WAN interface definitions at a site. Returns: id, name only (verified against 10.6.106 — the Integration API exposes no live link status or throughput rates here). Use for: WAN inventory, multi-WAN topology.",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
14
14
|
offset: zod_1.z
|
|
@@ -20,7 +20,7 @@ function registerSupportingTools(server, client) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -38,7 +38,7 @@ function registerSupportingTools(server, client) {
|
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
server.registerTool("unifi_list_vpn_tunnels", {
|
|
41
|
-
description: "List site-to-site VPN tunnels (IPsec, WireGuard, OpenVPN site-to-site) at a site. Returns: tunnel definitions per row (per-row schema not rendered in 10.
|
|
41
|
+
description: "List site-to-site VPN tunnels (IPsec, WireGuard, OpenVPN site-to-site) at a site. Returns: tunnel definitions per row (per-row schema not rendered in 10.6.106 docs — call to inspect). For roaming client VPN servers, see unifi_list_vpn_servers.",
|
|
42
42
|
inputSchema: {
|
|
43
43
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
44
44
|
offset: zod_1.z
|
|
@@ -50,7 +50,7 @@ function registerSupportingTools(server, client) {
|
|
|
50
50
|
limit: zod_1.z
|
|
51
51
|
.number()
|
|
52
52
|
.int()
|
|
53
|
-
.min(
|
|
53
|
+
.min(0)
|
|
54
54
|
.max(200)
|
|
55
55
|
.optional()
|
|
56
56
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -84,7 +84,7 @@ function registerSupportingTools(server, client) {
|
|
|
84
84
|
limit: zod_1.z
|
|
85
85
|
.number()
|
|
86
86
|
.int()
|
|
87
|
-
.min(
|
|
87
|
+
.min(0)
|
|
88
88
|
.max(200)
|
|
89
89
|
.optional()
|
|
90
90
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -118,7 +118,7 @@ function registerSupportingTools(server, client) {
|
|
|
118
118
|
limit: zod_1.z
|
|
119
119
|
.number()
|
|
120
120
|
.int()
|
|
121
|
-
.min(
|
|
121
|
+
.min(0)
|
|
122
122
|
.max(200)
|
|
123
123
|
.optional()
|
|
124
124
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -152,7 +152,7 @@ function registerSupportingTools(server, client) {
|
|
|
152
152
|
limit: zod_1.z
|
|
153
153
|
.number()
|
|
154
154
|
.int()
|
|
155
|
-
.min(
|
|
155
|
+
.min(0)
|
|
156
156
|
.max(200)
|
|
157
157
|
.optional()
|
|
158
158
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -185,7 +185,7 @@ function registerSupportingTools(server, client) {
|
|
|
185
185
|
limit: zod_1.z
|
|
186
186
|
.number()
|
|
187
187
|
.int()
|
|
188
|
-
.min(
|
|
188
|
+
.min(0)
|
|
189
189
|
.max(200)
|
|
190
190
|
.optional()
|
|
191
191
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -218,7 +218,7 @@ function registerSupportingTools(server, client) {
|
|
|
218
218
|
limit: zod_1.z
|
|
219
219
|
.number()
|
|
220
220
|
.int()
|
|
221
|
-
.min(
|
|
221
|
+
.min(0)
|
|
222
222
|
.max(200)
|
|
223
223
|
.optional()
|
|
224
224
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -251,14 +251,14 @@ function registerSupportingTools(server, client) {
|
|
|
251
251
|
limit: zod_1.z
|
|
252
252
|
.number()
|
|
253
253
|
.int()
|
|
254
|
-
.min(
|
|
254
|
+
.min(0)
|
|
255
255
|
.max(200)
|
|
256
256
|
.optional()
|
|
257
257
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
258
258
|
filter: zod_1.z
|
|
259
259
|
.string()
|
|
260
260
|
.optional()
|
|
261
|
-
.describe("Filter expression (e.g.,
|
|
261
|
+
.describe("Filter expression (e.g., name.like('United*'))"),
|
|
262
262
|
},
|
|
263
263
|
outputSchema: output_schemas_js_1.listCountriesOutputSchema,
|
|
264
264
|
annotations: safety_js_1.READ_ONLY,
|
package/dist/tools/switching.js
CHANGED
|
@@ -8,7 +8,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
8
8
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
9
9
|
function registerSwitchingTools(server, client) {
|
|
10
10
|
server.registerTool("unifi_list_switch_stacks", {
|
|
11
|
-
description: "List Switch Stacks (multiple physical switches managed as one logical unit) at a site. Returns: id, name,
|
|
11
|
+
description: "List Switch Stacks (multiple physical switches managed as one logical unit) at a site. Returns: id, deviceId (the stack's primary device), name, units[] (the stacked switches — named `members` on consoles older than 10.6.106), lags[] (LAGs spanning the stack), metadata.origin. Per-unit schema is not rendered in the 10.6.106 docs — call unifi_get_switch_stack to inspect. Use for: identifying stacked switches; individual member configs/stats still come from unifi_get_device.",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
14
14
|
offset: zod_1.z
|
|
@@ -20,7 +20,7 @@ function registerSwitchingTools(server, client) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -42,7 +42,7 @@ function registerSwitchingTools(server, client) {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
server.registerTool("unifi_get_switch_stack", {
|
|
45
|
-
description: "Get full details of a Switch Stack including all
|
|
45
|
+
description: "Get full details of a Switch Stack including all units and stacking topology. Returns the same fields as the list response but for a single stack: id, deviceId, name, units[] (named `members` before 10.6.106), lags[], metadata.origin.",
|
|
46
46
|
inputSchema: {
|
|
47
47
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
48
48
|
switchStackId: zod_1.z.string().describe("Switch Stack ID"),
|
|
@@ -71,7 +71,7 @@ function registerSwitchingTools(server, client) {
|
|
|
71
71
|
limit: zod_1.z
|
|
72
72
|
.number()
|
|
73
73
|
.int()
|
|
74
|
-
.min(
|
|
74
|
+
.min(0)
|
|
75
75
|
.max(200)
|
|
76
76
|
.optional()
|
|
77
77
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -122,7 +122,7 @@ function registerSwitchingTools(server, client) {
|
|
|
122
122
|
limit: zod_1.z
|
|
123
123
|
.number()
|
|
124
124
|
.int()
|
|
125
|
-
.min(
|
|
125
|
+
.min(0)
|
|
126
126
|
.max(200)
|
|
127
127
|
.optional()
|
|
128
128
|
.describe("Number of records to return (default: 25, max: 200)"),
|
package/dist/tools/system.js
CHANGED
|
@@ -6,7 +6,7 @@ const safety_js_1 = require("../utils/safety.js");
|
|
|
6
6
|
const output_schemas_js_1 = require("../utils/output-schemas.js");
|
|
7
7
|
function registerSystemTools(server, client) {
|
|
8
8
|
server.registerTool("unifi_get_info", {
|
|
9
|
-
description: "Get UniFi Network application info. Returns: applicationVersion. NOTE: verified against 10.
|
|
9
|
+
description: "Get UniFi Network application info. Returns: applicationVersion. NOTE: verified against 10.6.106 on a UniFi OS console — the Integration API returns ONLY applicationVersion here; there is no isUniFiOSConsole or other field. Use for: version checks before calling version-gated tools.",
|
|
10
10
|
inputSchema: {},
|
|
11
11
|
outputSchema: output_schemas_js_1.getInfoOutputSchema,
|
|
12
12
|
annotations: safety_js_1.READ_ONLY,
|
package/dist/tools/wifi.js
CHANGED
|
@@ -20,7 +20,7 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
20
20
|
limit: zod_1.z
|
|
21
21
|
.number()
|
|
22
22
|
.int()
|
|
23
|
-
.min(
|
|
23
|
+
.min(0)
|
|
24
24
|
.max(200)
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Number of records to return (default: 25, max: 200)"),
|
|
@@ -68,8 +68,8 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
68
68
|
enabled: zod_1.z.boolean().describe("Enable the WiFi network"),
|
|
69
69
|
type: zod_1.z.enum(["STANDARD", "IOT_OPTIMIZED"]).describe("WiFi type"),
|
|
70
70
|
broadcastingFrequenciesGHz: zod_1.z
|
|
71
|
-
.array(zod_1.z.
|
|
72
|
-
.describe("Frequencies
|
|
71
|
+
.array(zod_1.z.enum(["2.4", "5", "6"]))
|
|
72
|
+
.describe("Frequencies to broadcast on. Accepted as strings and sent to the API as numbers (2.4, 5, 6)."),
|
|
73
73
|
securityConfiguration: zod_1.z
|
|
74
74
|
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
75
75
|
.describe("Security configuration object"),
|
|
@@ -209,7 +209,7 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
209
209
|
}
|
|
210
210
|
});
|
|
211
211
|
server.registerTool("unifi_update_wifi", {
|
|
212
|
-
description: "Update an existing WiFi network",
|
|
212
|
+
description: "Update an existing WiFi network. NOTE: this is a full PUT — API 10.6.106 documents 14 of these fields as required (type, name, enabled, securityConfiguration, multicastToUnicastConversionEnabled, clientIsolationEnabled, hideName, uapsdEnabled, channel2gLockedTo6, dtimPeriod2gLockedTo3, broadcastingFrequenciesGHz, arpProxyEnabled, bssTransitionEnabled, advertiseDeviceName). They are optional here for convenience, but a sparse update may be rejected by the API — read the current values with unifi_get_wifi and resend the full set if you get a 400.",
|
|
213
213
|
inputSchema: {
|
|
214
214
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
215
215
|
wifiBroadcastId: zod_1.z.string().describe("WiFi Broadcast ID"),
|
|
@@ -220,9 +220,9 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
220
220
|
.optional()
|
|
221
221
|
.describe("WiFi type"),
|
|
222
222
|
broadcastingFrequenciesGHz: zod_1.z
|
|
223
|
-
.array(zod_1.z.
|
|
223
|
+
.array(zod_1.z.enum(["2.4", "5", "6"]))
|
|
224
224
|
.optional()
|
|
225
|
-
.describe("Frequencies
|
|
225
|
+
.describe("Frequencies to broadcast on. Accepted as strings and sent to the API as numbers (2.4, 5, 6)."),
|
|
226
226
|
securityConfiguration: zod_1.z
|
|
227
227
|
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
228
228
|
.optional()
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* field optional, every nested object uses .passthrough() to allow
|
|
4
4
|
* firmware/hardware-specific fields to flow through unchanged).
|
|
5
5
|
*
|
|
6
|
-
* Verified against UniFi Network API 10.
|
|
6
|
+
* Verified against UniFi Network API 10.6.106. Where the docs collapse
|
|
7
7
|
* nested arrays/objects (e.g. interfaces.ports[], radios[]), the schema
|
|
8
8
|
* uses passthrough records so the contract doesn't lock to fields we
|
|
9
9
|
* haven't verified.
|
|
@@ -37,6 +37,7 @@ export declare const networkOutputSchema: {
|
|
|
37
37
|
cellularBackupEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
38
38
|
dhcpGuarding: z.ZodOptional<z.ZodUnknown>;
|
|
39
39
|
ipv4Configuration: z.ZodOptional<z.ZodUnknown>;
|
|
40
|
+
ipv6Configuration: z.ZodOptional<z.ZodUnknown>;
|
|
40
41
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
41
42
|
origin: z.ZodOptional<z.ZodString>;
|
|
42
43
|
}, z.core.$loose>>;
|
|
@@ -60,6 +61,7 @@ export declare const listNetworksOutputSchema: {
|
|
|
60
61
|
cellularBackupEnabled: z.ZodOptional<z.ZodBoolean>;
|
|
61
62
|
dhcpGuarding: z.ZodOptional<z.ZodUnknown>;
|
|
62
63
|
ipv4Configuration: z.ZodOptional<z.ZodUnknown>;
|
|
64
|
+
ipv6Configuration: z.ZodOptional<z.ZodUnknown>;
|
|
63
65
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
64
66
|
origin: z.ZodOptional<z.ZodString>;
|
|
65
67
|
}, z.core.$loose>>;
|
|
@@ -202,6 +204,7 @@ export declare const aclRuleOutputSchema: {
|
|
|
202
204
|
sourceFilter: z.ZodOptional<z.ZodUnknown>;
|
|
203
205
|
destinationFilter: z.ZodOptional<z.ZodUnknown>;
|
|
204
206
|
enforcingDeviceFilter: z.ZodOptional<z.ZodUnknown>;
|
|
207
|
+
index: z.ZodOptional<z.ZodNumber>;
|
|
205
208
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
206
209
|
origin: z.ZodOptional<z.ZodString>;
|
|
207
210
|
}, z.core.$loose>>;
|
|
@@ -222,6 +225,7 @@ export declare const listAclRulesOutputSchema: {
|
|
|
222
225
|
sourceFilter: z.ZodOptional<z.ZodUnknown>;
|
|
223
226
|
destinationFilter: z.ZodOptional<z.ZodUnknown>;
|
|
224
227
|
enforcingDeviceFilter: z.ZodOptional<z.ZodUnknown>;
|
|
228
|
+
index: z.ZodOptional<z.ZodNumber>;
|
|
225
229
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
226
230
|
origin: z.ZodOptional<z.ZodString>;
|
|
227
231
|
}, z.core.$loose>>;
|
|
@@ -233,6 +237,8 @@ export declare const aclRuleOrderingOutputSchema: {
|
|
|
233
237
|
export declare const switchStackOutputSchema: {
|
|
234
238
|
id: z.ZodString;
|
|
235
239
|
name: z.ZodOptional<z.ZodString>;
|
|
240
|
+
deviceId: z.ZodOptional<z.ZodString>;
|
|
241
|
+
units: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
236
242
|
members: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
237
243
|
lags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
238
244
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
@@ -247,6 +253,8 @@ export declare const listSwitchStacksOutputSchema: {
|
|
|
247
253
|
readonly data: z.ZodArray<z.ZodObject<{
|
|
248
254
|
id: z.ZodString;
|
|
249
255
|
name: z.ZodOptional<z.ZodString>;
|
|
256
|
+
deviceId: z.ZodOptional<z.ZodString>;
|
|
257
|
+
units: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
250
258
|
members: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
251
259
|
lags: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
252
260
|
metadata: z.ZodOptional<z.ZodObject<{
|
|
@@ -607,3 +615,73 @@ export declare const listPendingDevicesOutputSchema: {
|
|
|
607
615
|
firmwareVersion: z.ZodOptional<z.ZodString>;
|
|
608
616
|
}, z.core.$loose>>;
|
|
609
617
|
};
|
|
618
|
+
export declare const HistoryClient: z.ZodObject<{
|
|
619
|
+
mac: z.ZodString;
|
|
620
|
+
hostname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
621
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
622
|
+
first_seen: z.ZodOptional<z.ZodNumber>;
|
|
623
|
+
last_seen: z.ZodOptional<z.ZodNumber>;
|
|
624
|
+
is_wired: z.ZodOptional<z.ZodBoolean>;
|
|
625
|
+
status: z.ZodOptional<z.ZodString>;
|
|
626
|
+
}, z.core.$loose>;
|
|
627
|
+
export declare const ClientSession: z.ZodObject<{
|
|
628
|
+
_id: z.ZodString;
|
|
629
|
+
mac: z.ZodString;
|
|
630
|
+
assoc_time: z.ZodNumber;
|
|
631
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
632
|
+
rx_bytes: z.ZodOptional<z.ZodNumber>;
|
|
633
|
+
tx_bytes: z.ZodOptional<z.ZodNumber>;
|
|
634
|
+
hostname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
635
|
+
is_wired: z.ZodOptional<z.ZodBoolean>;
|
|
636
|
+
roaming_sessions: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
637
|
+
}, z.core.$loose>;
|
|
638
|
+
/** Envelope returned by the controller's stat/session endpoint. */
|
|
639
|
+
export declare const ClientSessionEnvelope: z.ZodObject<{
|
|
640
|
+
meta: z.ZodObject<{
|
|
641
|
+
rc: z.ZodLiteral<"ok">;
|
|
642
|
+
}, z.core.$loose>;
|
|
643
|
+
data: z.ZodArray<z.ZodObject<{
|
|
644
|
+
_id: z.ZodString;
|
|
645
|
+
mac: z.ZodString;
|
|
646
|
+
assoc_time: z.ZodNumber;
|
|
647
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
648
|
+
rx_bytes: z.ZodOptional<z.ZodNumber>;
|
|
649
|
+
tx_bytes: z.ZodOptional<z.ZodNumber>;
|
|
650
|
+
hostname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
651
|
+
is_wired: z.ZodOptional<z.ZodBoolean>;
|
|
652
|
+
roaming_sessions: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
653
|
+
}, z.core.$loose>>;
|
|
654
|
+
}, z.core.$strip>;
|
|
655
|
+
export declare const listClientHistoryOutputSchema: {
|
|
656
|
+
readonly data: z.ZodArray<z.ZodObject<{
|
|
657
|
+
mac: z.ZodString;
|
|
658
|
+
hostname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
659
|
+
display_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
660
|
+
first_seen: z.ZodOptional<z.ZodNumber>;
|
|
661
|
+
last_seen: z.ZodOptional<z.ZodNumber>;
|
|
662
|
+
is_wired: z.ZodOptional<z.ZodBoolean>;
|
|
663
|
+
status: z.ZodOptional<z.ZodString>;
|
|
664
|
+
}, z.core.$loose>>;
|
|
665
|
+
readonly count: z.ZodNumber;
|
|
666
|
+
readonly withinHours: z.ZodNumber;
|
|
667
|
+
readonly coverage: z.ZodString;
|
|
668
|
+
};
|
|
669
|
+
export declare const listClientSessionsOutputSchema: {
|
|
670
|
+
readonly data: z.ZodArray<z.ZodObject<{
|
|
671
|
+
_id: z.ZodString;
|
|
672
|
+
mac: z.ZodString;
|
|
673
|
+
assoc_time: z.ZodNumber;
|
|
674
|
+
duration: z.ZodOptional<z.ZodNumber>;
|
|
675
|
+
rx_bytes: z.ZodOptional<z.ZodNumber>;
|
|
676
|
+
tx_bytes: z.ZodOptional<z.ZodNumber>;
|
|
677
|
+
hostname: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
678
|
+
is_wired: z.ZodOptional<z.ZodBoolean>;
|
|
679
|
+
roaming_sessions: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
680
|
+
}, z.core.$loose>>;
|
|
681
|
+
readonly count: z.ZodNumber;
|
|
682
|
+
readonly limit: z.ZodNumber;
|
|
683
|
+
readonly mayBeTruncated: z.ZodBoolean;
|
|
684
|
+
readonly start: z.ZodNumber;
|
|
685
|
+
readonly end: z.ZodNumber;
|
|
686
|
+
readonly coverage: z.ZodString;
|
|
687
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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;
|
|
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
4
|
/**
|
|
5
5
|
* Output schemas for high-value read tools (loose strategy: every non-key
|
|
6
6
|
* field optional, every nested object uses .passthrough() to allow
|
|
7
7
|
* firmware/hardware-specific fields to flow through unchanged).
|
|
8
8
|
*
|
|
9
|
-
* Verified against UniFi Network API 10.
|
|
9
|
+
* Verified against UniFi Network API 10.6.106. Where the docs collapse
|
|
10
10
|
* nested arrays/objects (e.g. interfaces.ports[], radios[]), the schema
|
|
11
11
|
* uses passthrough records so the contract doesn't lock to fields we
|
|
12
12
|
* haven't verified.
|
|
@@ -160,7 +160,7 @@ const WifiBroadcast = zod_1.z
|
|
|
160
160
|
.passthrough();
|
|
161
161
|
const ApplicationInfo = zod_1.z
|
|
162
162
|
.object({
|
|
163
|
-
// Live-verified (10.
|
|
163
|
+
// Live-verified (10.6.106): only applicationVersion is returned.
|
|
164
164
|
applicationVersion: zod_1.z.string().optional(),
|
|
165
165
|
})
|
|
166
166
|
.passthrough();
|
|
@@ -185,8 +185,9 @@ const Network = zod_1.z
|
|
|
185
185
|
mdnsForwardingEnabled: zod_1.z.boolean().optional(),
|
|
186
186
|
cellularBackupEnabled: zod_1.z.boolean().optional(),
|
|
187
187
|
dhcpGuarding: zod_1.z.unknown().optional(),
|
|
188
|
-
// get-by-id only (absent in list view); nested
|
|
188
|
+
// get-by-id only (absent in list view); nested shapes left loose.
|
|
189
189
|
ipv4Configuration: zod_1.z.unknown().optional(),
|
|
190
|
+
ipv6Configuration: zod_1.z.unknown().optional(),
|
|
190
191
|
metadata: Metadata.optional(),
|
|
191
192
|
})
|
|
192
193
|
.passthrough();
|
|
@@ -263,6 +264,9 @@ const AclRule = zod_1.z
|
|
|
263
264
|
sourceFilter: zod_1.z.unknown().optional(),
|
|
264
265
|
destinationFilter: zod_1.z.unknown().optional(),
|
|
265
266
|
enforcingDeviceFilter: zod_1.z.unknown().optional(),
|
|
267
|
+
// Read-only in the response. Deprecated as a *request* field — ordering
|
|
268
|
+
// is set via the dedicated reorder endpoint, so no write tool accepts it.
|
|
269
|
+
index: zod_1.z.number().int().optional(),
|
|
266
270
|
metadata: Metadata.optional(),
|
|
267
271
|
})
|
|
268
272
|
.passthrough();
|
|
@@ -275,6 +279,12 @@ const SwitchStack = zod_1.z
|
|
|
275
279
|
.object({
|
|
276
280
|
id: zod_1.z.string(),
|
|
277
281
|
name: zod_1.z.string().optional(),
|
|
282
|
+
// The stack's primary device. Added in 10.6.106.
|
|
283
|
+
deviceId: zod_1.z.string().optional(),
|
|
284
|
+
// Renamed from `members` in 10.6.106 (was `members` through 10.4.57).
|
|
285
|
+
// `members` stays declared so responses from older consoles still
|
|
286
|
+
// describe themselves; both are optional, so either shape validates.
|
|
287
|
+
units: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
278
288
|
members: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
279
289
|
lags: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
280
290
|
metadata: Metadata.optional(),
|
|
@@ -324,7 +334,7 @@ const TrafficMatchingList = zod_1.z
|
|
|
324
334
|
metadata: Metadata.optional(),
|
|
325
335
|
})
|
|
326
336
|
.passthrough();
|
|
327
|
-
// Verified against the live Integration API (10.
|
|
337
|
+
// Verified against the live Integration API (10.6.106). The WAN and
|
|
328
338
|
// site-to-site-tunnel list rows are intentionally minimal in the API.
|
|
329
339
|
const Wan = zod_1.z
|
|
330
340
|
.object({
|
|
@@ -509,3 +519,50 @@ exports.listPendingDevicesOutputSchema = {
|
|
|
509
519
|
...PaginationFields,
|
|
510
520
|
data: zod_1.z.array(PendingDevice),
|
|
511
521
|
};
|
|
522
|
+
// Controller history endpoints (v2 clients/history and stat/session) are not
|
|
523
|
+
// part of the Integration API, so these follow the same loose strategy against
|
|
524
|
+
// the controller's own response shape: only the identity fields are required.
|
|
525
|
+
exports.HistoryClient = zod_1.z
|
|
526
|
+
.object({
|
|
527
|
+
mac: zod_1.z.string(),
|
|
528
|
+
hostname: zod_1.z.string().nullish(),
|
|
529
|
+
display_name: zod_1.z.string().nullish(),
|
|
530
|
+
first_seen: zod_1.z.number().optional(),
|
|
531
|
+
last_seen: zod_1.z.number().optional(),
|
|
532
|
+
is_wired: zod_1.z.boolean().optional(),
|
|
533
|
+
status: zod_1.z.string().optional(),
|
|
534
|
+
})
|
|
535
|
+
.passthrough();
|
|
536
|
+
exports.ClientSession = zod_1.z
|
|
537
|
+
.object({
|
|
538
|
+
_id: zod_1.z.string(),
|
|
539
|
+
mac: zod_1.z.string(),
|
|
540
|
+
assoc_time: zod_1.z.number(),
|
|
541
|
+
duration: zod_1.z.number().optional(),
|
|
542
|
+
rx_bytes: zod_1.z.number().optional(),
|
|
543
|
+
tx_bytes: zod_1.z.number().optional(),
|
|
544
|
+
hostname: zod_1.z.string().nullish(),
|
|
545
|
+
is_wired: zod_1.z.boolean().optional(),
|
|
546
|
+
roaming_sessions: zod_1.z.array(zod_1.z.unknown()).optional(),
|
|
547
|
+
})
|
|
548
|
+
.passthrough();
|
|
549
|
+
/** Envelope returned by the controller's stat/session endpoint. */
|
|
550
|
+
exports.ClientSessionEnvelope = zod_1.z.object({
|
|
551
|
+
meta: zod_1.z.object({ rc: zod_1.z.literal("ok") }).passthrough(),
|
|
552
|
+
data: zod_1.z.array(exports.ClientSession),
|
|
553
|
+
});
|
|
554
|
+
exports.listClientHistoryOutputSchema = {
|
|
555
|
+
data: zod_1.z.array(exports.HistoryClient),
|
|
556
|
+
count: zod_1.z.number(),
|
|
557
|
+
withinHours: zod_1.z.number(),
|
|
558
|
+
coverage: zod_1.z.string(),
|
|
559
|
+
};
|
|
560
|
+
exports.listClientSessionsOutputSchema = {
|
|
561
|
+
data: zod_1.z.array(exports.ClientSession),
|
|
562
|
+
count: zod_1.z.number(),
|
|
563
|
+
limit: zod_1.z.number(),
|
|
564
|
+
mayBeTruncated: zod_1.z.boolean(),
|
|
565
|
+
start: zod_1.z.number(),
|
|
566
|
+
end: zod_1.z.number(),
|
|
567
|
+
coverage: zod_1.z.string(),
|
|
568
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owine/unifi-network-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "MCP server for the UniFi Network API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": "^22.13.0 || ^24.0.0"
|
|
24
24
|
},
|
|
25
|
-
"packageManager": "pnpm@12.
|
|
25
|
+
"packageManager": "pnpm@12.5.1",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
28
|
"url": "git+https://github.com/owine/unifi-network-mcp.git"
|
|
@@ -38,17 +38,17 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
41
|
-
"zod": "4.5
|
|
41
|
+
"zod": "4.6.5"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@eslint/js": "10.0.1",
|
|
45
|
-
"@types/node": "24.13.
|
|
45
|
+
"@types/node": "24.13.6",
|
|
46
46
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
47
|
-
"@vitest/coverage-v8": "
|
|
48
|
-
"eslint": "10.
|
|
49
|
-
"lefthook": "2.1.
|
|
47
|
+
"@vitest/coverage-v8": "5.0.1",
|
|
48
|
+
"eslint": "10.11.0",
|
|
49
|
+
"lefthook": "2.1.14",
|
|
50
50
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
51
|
-
"typescript-eslint": "8.
|
|
52
|
-
"vitest": "
|
|
51
|
+
"typescript-eslint": "8.70.0",
|
|
52
|
+
"vitest": "5.0.1"
|
|
53
53
|
}
|
|
54
54
|
}
|