@owine/unifi-network-mcp 2.6.0 → 2.7.1

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.
@@ -5,9 +5,10 @@ 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 output_schemas_js_1 = require("../utils/output-schemas.js");
8
9
  function registerNetworkTools(server, client, readOnly = false) {
9
10
  server.registerTool("unifi_list_networks", {
10
- description: "List all networks at a site",
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
12
  inputSchema: {
12
13
  siteId: zod_1.z.string().describe("Site ID"),
13
14
  offset: zod_1.z
@@ -28,44 +29,47 @@ function registerNetworkTools(server, client, readOnly = false) {
28
29
  .optional()
29
30
  .describe("Filter expression"),
30
31
  },
32
+ outputSchema: output_schemas_js_1.listNetworksOutputSchema,
31
33
  annotations: safety_js_1.READ_ONLY,
32
34
  }, async ({ siteId, offset, limit, filter }) => {
33
35
  try {
34
36
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
35
37
  const data = await client.get(`/sites/${siteId}/networks${query}`);
36
- return (0, responses_js_1.formatSuccess)(data);
38
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
37
39
  }
38
40
  catch (err) {
39
41
  return (0, responses_js_1.formatError)(err);
40
42
  }
41
43
  });
42
44
  server.registerTool("unifi_get_network", {
43
- description: "Get a specific network by ID",
45
+ description: "Get a network/VLAN by ID. Returns the list fields PLUS (live-verified) zoneId, isolationEnabled, internetAccessEnabled, mdnsForwardingEnabled, cellularBackupEnabled, and a full ipv4Configuration object (hostIpAddress, prefixLength, dhcpConfiguration with ipAddressRange/leaseTimeSeconds/domainName/ntpServerIpAddresses). NOTE: subnet/DHCP detail appears here at get-by-id but NOT in unifi_list_networks (sparse list view).",
44
46
  inputSchema: {
45
47
  siteId: zod_1.z.string().describe("Site ID"),
46
48
  networkId: zod_1.z.string().describe("Network ID"),
47
49
  },
50
+ outputSchema: output_schemas_js_1.networkOutputSchema,
48
51
  annotations: safety_js_1.READ_ONLY,
49
52
  }, async ({ siteId, networkId }) => {
50
53
  try {
51
54
  const data = await client.get(`/sites/${siteId}/networks/${networkId}`);
52
- return (0, responses_js_1.formatSuccess)(data);
55
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
53
56
  }
54
57
  catch (err) {
55
58
  return (0, responses_js_1.formatError)(err);
56
59
  }
57
60
  });
58
61
  server.registerTool("unifi_get_network_references", {
59
- description: "Get references to a network (what WiFi broadcasts, firewall zones, etc. use this network)",
62
+ description: "Get all objects that reference this network (WiFi broadcasts, firewall zones, etc.). Returns: { referenceResources: [...] }. Use before deleting a network to find dependencies that need to be re-pointed or removed.",
60
63
  inputSchema: {
61
64
  siteId: zod_1.z.string().describe("Site ID"),
62
65
  networkId: zod_1.z.string().describe("Network ID"),
63
66
  },
67
+ outputSchema: output_schemas_js_1.getNetworkReferencesOutputSchema,
64
68
  annotations: safety_js_1.READ_ONLY,
65
69
  }, async ({ siteId, networkId }) => {
66
70
  try {
67
71
  const data = await client.get(`/sites/${siteId}/networks/${networkId}/references`);
68
- return (0, responses_js_1.formatSuccess)(data);
72
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
69
73
  }
70
74
  catch (err) {
71
75
  return (0, responses_js_1.formatError)(err);
@@ -74,7 +78,7 @@ function registerNetworkTools(server, client, readOnly = false) {
74
78
  if (readOnly)
75
79
  return;
76
80
  server.registerTool("unifi_create_network", {
77
- description: "Create a new network",
81
+ description: "Create a new VLAN/network. management=GATEWAY means routed via UDM/UXG; SWITCH means VLAN-only; UNMANAGED means external. Idempotency: not safe to retry — re-running creates duplicates.",
78
82
  inputSchema: {
79
83
  siteId: zod_1.z.string().describe("Site ID"),
80
84
  name: zod_1.z.string().describe("Network name"),
@@ -97,6 +101,7 @@ function registerNetworkTools(server, client, readOnly = false) {
97
101
  .optional()
98
102
  .describe("Preview this action without executing it"),
99
103
  },
104
+ outputSchema: output_schemas_js_1.networkOutputSchema,
100
105
  annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
101
106
  }, async ({ siteId, name, management, enabled, vlanId, dhcpGuarding, dryRun }) => {
102
107
  try {
@@ -106,7 +111,7 @@ function registerNetworkTools(server, client, readOnly = false) {
106
111
  if (dryRun)
107
112
  return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/networks`, body);
108
113
  const data = await client.post(`/sites/${siteId}/networks`, body);
109
- return (0, responses_js_1.formatSuccess)(data);
114
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
110
115
  }
111
116
  catch (err) {
112
117
  return (0, responses_js_1.formatError)(err);
@@ -137,6 +142,7 @@ function registerNetworkTools(server, client, readOnly = false) {
137
142
  .optional()
138
143
  .describe("Preview this action without executing it"),
139
144
  },
145
+ outputSchema: output_schemas_js_1.networkOutputSchema,
140
146
  annotations: safety_js_1.WRITE,
141
147
  }, async ({ siteId, networkId, name, management, enabled, vlanId, dhcpGuarding, dryRun }) => {
142
148
  try {
@@ -146,7 +152,7 @@ function registerNetworkTools(server, client, readOnly = false) {
146
152
  if (dryRun)
147
153
  return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/networks/${networkId}`, body);
148
154
  const data = await client.put(`/sites/${siteId}/networks/${networkId}`, body);
149
- return (0, responses_js_1.formatSuccess)(data);
155
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
150
156
  }
151
157
  catch (err) {
152
158
  return (0, responses_js_1.formatError)(err);
@@ -5,9 +5,10 @@ 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 output_schemas_js_1 = require("../utils/output-schemas.js");
8
9
  function registerSiteTools(server, client) {
9
10
  server.registerTool("unifi_list_sites", {
10
- description: "List all sites available to the API key",
11
+ description: "List all sites the API key has access to. Returns: id (the siteId every other tool requires), internalReference, name. Use for: first call in any workflow — almost every other tool needs a siteId.",
11
12
  inputSchema: {
12
13
  offset: zod_1.z
13
14
  .number()
@@ -27,12 +28,13 @@ function registerSiteTools(server, client) {
27
28
  .optional()
28
29
  .describe("Filter expression (e.g., 'name.like(office*)')"),
29
30
  },
31
+ outputSchema: output_schemas_js_1.listSitesOutputSchema,
30
32
  annotations: safety_js_1.READ_ONLY,
31
33
  }, async ({ offset, limit, filter }) => {
32
34
  try {
33
35
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
34
36
  const data = await client.get(`/sites${query}`);
35
- return (0, responses_js_1.formatSuccess)(data);
37
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
36
38
  }
37
39
  catch (err) {
38
40
  return (0, responses_js_1.formatError)(err);
@@ -5,9 +5,10 @@ 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 output_schemas_js_1 = require("../utils/output-schemas.js");
8
9
  function registerSupportingTools(server, client) {
9
10
  server.registerTool("unifi_list_wans", {
10
- description: "List all WAN interfaces at a site",
11
+ description: "List WAN interface definitions at a site. Returns: id, name only (verified against 10.4.55 — the Integration API exposes no live link status or throughput rates here). Use for: WAN inventory, multi-WAN topology.",
11
12
  inputSchema: {
12
13
  siteId: zod_1.z.string().describe("Site ID"),
13
14
  offset: zod_1.z
@@ -24,19 +25,20 @@ function registerSupportingTools(server, client) {
24
25
  .optional()
25
26
  .describe("Number of records to return (default: 25, max: 200)"),
26
27
  },
28
+ outputSchema: output_schemas_js_1.listWansOutputSchema,
27
29
  annotations: safety_js_1.READ_ONLY,
28
30
  }, async ({ siteId, offset, limit }) => {
29
31
  try {
30
32
  const query = (0, query_js_1.buildQuery)({ offset, limit });
31
33
  const data = await client.get(`/sites/${siteId}/wans${query}`);
32
- return (0, responses_js_1.formatSuccess)(data);
34
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
33
35
  }
34
36
  catch (err) {
35
37
  return (0, responses_js_1.formatError)(err);
36
38
  }
37
39
  });
38
40
  server.registerTool("unifi_list_vpn_tunnels", {
39
- description: "List all site-to-site VPN tunnels at a site",
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.4.55 docs — call to inspect). For roaming client VPN servers, see unifi_list_vpn_servers.",
40
42
  inputSchema: {
41
43
  siteId: zod_1.z.string().describe("Site ID"),
42
44
  offset: zod_1.z
@@ -57,19 +59,20 @@ function registerSupportingTools(server, client) {
57
59
  .optional()
58
60
  .describe("Filter expression"),
59
61
  },
62
+ outputSchema: output_schemas_js_1.listVpnTunnelsOutputSchema,
60
63
  annotations: safety_js_1.READ_ONLY,
61
64
  }, async ({ siteId, offset, limit, filter }) => {
62
65
  try {
63
66
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
64
67
  const data = await client.get(`/sites/${siteId}/vpn/site-to-site-tunnels${query}`);
65
- return (0, responses_js_1.formatSuccess)(data);
68
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
66
69
  }
67
70
  catch (err) {
68
71
  return (0, responses_js_1.formatError)(err);
69
72
  }
70
73
  });
71
74
  server.registerTool("unifi_list_vpn_servers", {
72
- description: "List all VPN servers at a site",
75
+ description: "List VPN servers (roaming/client-access VPNs: WireGuard, OpenVPN, L2TP, Teleport) at a site. Returns: id, type (e.g. WIREGUARD, UID), name, enabled, metadata.origin.",
73
76
  inputSchema: {
74
77
  siteId: zod_1.z.string().describe("Site ID"),
75
78
  offset: zod_1.z
@@ -90,19 +93,20 @@ function registerSupportingTools(server, client) {
90
93
  .optional()
91
94
  .describe("Filter expression"),
92
95
  },
96
+ outputSchema: output_schemas_js_1.listVpnServersOutputSchema,
93
97
  annotations: safety_js_1.READ_ONLY,
94
98
  }, async ({ siteId, offset, limit, filter }) => {
95
99
  try {
96
100
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
97
101
  const data = await client.get(`/sites/${siteId}/vpn/servers${query}`);
98
- return (0, responses_js_1.formatSuccess)(data);
102
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
99
103
  }
100
104
  catch (err) {
101
105
  return (0, responses_js_1.formatError)(err);
102
106
  }
103
107
  });
104
108
  server.registerTool("unifi_list_radius_profiles", {
105
- description: "List all RADIUS profiles at a site",
109
+ description: "List RADIUS profiles (auth/accounting server configurations referenced by WiFi WPA-Enterprise, switch 802.1X port auth, VPN). Returns: id, name, metadata (origin, configurable).",
106
110
  inputSchema: {
107
111
  siteId: zod_1.z.string().describe("Site ID"),
108
112
  offset: zod_1.z
@@ -123,19 +127,20 @@ function registerSupportingTools(server, client) {
123
127
  .optional()
124
128
  .describe("Filter expression"),
125
129
  },
130
+ outputSchema: output_schemas_js_1.listRadiusProfilesOutputSchema,
126
131
  annotations: safety_js_1.READ_ONLY,
127
132
  }, async ({ siteId, offset, limit, filter }) => {
128
133
  try {
129
134
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
130
135
  const data = await client.get(`/sites/${siteId}/radius/profiles${query}`);
131
- return (0, responses_js_1.formatSuccess)(data);
136
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
132
137
  }
133
138
  catch (err) {
134
139
  return (0, responses_js_1.formatError)(err);
135
140
  }
136
141
  });
137
142
  server.registerTool("unifi_list_device_tags", {
138
- description: "List all device tags at a site (used for WiFi broadcast assignments)",
143
+ description: "List device tags at a site. Tags group APs/switches for selective WiFi broadcast (via broadcastingDeviceFilter on a WiFi network). Returns: id, name, deviceIds[].",
139
144
  inputSchema: {
140
145
  siteId: zod_1.z.string().describe("Site ID"),
141
146
  offset: zod_1.z
@@ -156,19 +161,20 @@ function registerSupportingTools(server, client) {
156
161
  .optional()
157
162
  .describe("Filter expression"),
158
163
  },
164
+ outputSchema: output_schemas_js_1.listDeviceTagsOutputSchema,
159
165
  annotations: safety_js_1.READ_ONLY,
160
166
  }, async ({ siteId, offset, limit, filter }) => {
161
167
  try {
162
168
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
163
169
  const data = await client.get(`/sites/${siteId}/device-tags${query}`);
164
- return (0, responses_js_1.formatSuccess)(data);
170
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
165
171
  }
166
172
  catch (err) {
167
173
  return (0, responses_js_1.formatError)(err);
168
174
  }
169
175
  });
170
176
  server.registerTool("unifi_list_dpi_categories", {
171
- description: "List all DPI (Deep Packet Inspection) categories for traffic identification",
177
+ description: "List DPI categories (global, not site-scoped) high-level traffic groupings like 'Streaming', 'Social Networks', 'Gaming'. Returns: id (numeric), name. Use the category id when building firewall policies that match by category.",
172
178
  inputSchema: {
173
179
  offset: zod_1.z
174
180
  .number()
@@ -188,19 +194,20 @@ function registerSupportingTools(server, client) {
188
194
  .optional()
189
195
  .describe("Filter expression"),
190
196
  },
197
+ outputSchema: output_schemas_js_1.listDpiCategoriesOutputSchema,
191
198
  annotations: safety_js_1.READ_ONLY,
192
199
  }, async ({ offset, limit, filter }) => {
193
200
  try {
194
201
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
195
202
  const data = await client.get(`/dpi/categories${query}`);
196
- return (0, responses_js_1.formatSuccess)(data);
203
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
197
204
  }
198
205
  catch (err) {
199
206
  return (0, responses_js_1.formatError)(err);
200
207
  }
201
208
  });
202
209
  server.registerTool("unifi_list_dpi_applications", {
203
- description: "List all DPI applications for traffic identification and filtering",
210
+ description: "List individual DPI applications (global) specific apps/services like 'Netflix', 'Zoom', 'Steam'. Returns: id (numeric), name. More granular than unifi_list_dpi_categories.",
204
211
  inputSchema: {
205
212
  offset: zod_1.z
206
213
  .number()
@@ -220,19 +227,20 @@ function registerSupportingTools(server, client) {
220
227
  .optional()
221
228
  .describe("Filter expression"),
222
229
  },
230
+ outputSchema: output_schemas_js_1.listDpiApplicationsOutputSchema,
223
231
  annotations: safety_js_1.READ_ONLY,
224
232
  }, async ({ offset, limit, filter }) => {
225
233
  try {
226
234
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
227
235
  const data = await client.get(`/dpi/applications${query}`);
228
- return (0, responses_js_1.formatSuccess)(data);
236
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
229
237
  }
230
238
  catch (err) {
231
239
  return (0, responses_js_1.formatError)(err);
232
240
  }
233
241
  });
234
242
  server.registerTool("unifi_list_countries", {
235
- description: "List all countries/regions for geo-based rules (ISO codes and names)",
243
+ description: "List countries/regions (global) for geo-IP firewall rules. Returns: code (ISO alpha-2, e.g. 'US'), name. Use the code when building firewall policies that match by source/destination country.",
236
244
  inputSchema: {
237
245
  offset: zod_1.z
238
246
  .number()
@@ -252,12 +260,13 @@ function registerSupportingTools(server, client) {
252
260
  .optional()
253
261
  .describe("Filter expression (e.g., 'name.like(United*)')"),
254
262
  },
263
+ outputSchema: output_schemas_js_1.listCountriesOutputSchema,
255
264
  annotations: safety_js_1.READ_ONLY,
256
265
  }, async ({ offset, limit, filter }) => {
257
266
  try {
258
267
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
259
268
  const data = await client.get(`/countries${query}`);
260
- return (0, responses_js_1.formatSuccess)(data);
269
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
261
270
  }
262
271
  catch (err) {
263
272
  return (0, responses_js_1.formatError)(err);
@@ -5,9 +5,10 @@ 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 output_schemas_js_1 = require("../utils/output-schemas.js");
8
9
  function registerSwitchingTools(server, client) {
9
10
  server.registerTool("unifi_list_switch_stacks", {
10
- description: "List all Switch Stacks at a site",
11
+ description: "List Switch Stacks (multiple physical switches managed as one logical unit) at a site. Returns: id, name, members[], lags[] (LAGs spanning the stack), metadata.origin. Use for: identifying stacked switches; individual member configs/stats still come from unifi_get_device.",
11
12
  inputSchema: {
12
13
  siteId: zod_1.z.string().describe("Site ID"),
13
14
  offset: zod_1.z
@@ -28,35 +29,37 @@ function registerSwitchingTools(server, client) {
28
29
  .optional()
29
30
  .describe("Filter expression"),
30
31
  },
32
+ outputSchema: output_schemas_js_1.listSwitchStacksOutputSchema,
31
33
  annotations: safety_js_1.READ_ONLY,
32
34
  }, async ({ siteId, offset, limit, filter }) => {
33
35
  try {
34
36
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
35
37
  const data = await client.get(`/sites/${siteId}/switching/switch-stacks${query}`);
36
- return (0, responses_js_1.formatSuccess)(data);
38
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
37
39
  }
38
40
  catch (err) {
39
41
  return (0, responses_js_1.formatError)(err);
40
42
  }
41
43
  });
42
44
  server.registerTool("unifi_get_switch_stack", {
43
- description: "Get details of a specific Switch Stack",
45
+ description: "Get full details of a Switch Stack including all members and stacking topology. Returns the same fields as the list response but for a single stack.",
44
46
  inputSchema: {
45
47
  siteId: zod_1.z.string().describe("Site ID"),
46
48
  switchStackId: zod_1.z.string().describe("Switch Stack ID"),
47
49
  },
50
+ outputSchema: output_schemas_js_1.switchStackOutputSchema,
48
51
  annotations: safety_js_1.READ_ONLY,
49
52
  }, async ({ siteId, switchStackId }) => {
50
53
  try {
51
54
  const data = await client.get(`/sites/${siteId}/switching/switch-stacks/${switchStackId}`);
52
- return (0, responses_js_1.formatSuccess)(data);
55
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
53
56
  }
54
57
  catch (err) {
55
58
  return (0, responses_js_1.formatError)(err);
56
59
  }
57
60
  });
58
61
  server.registerTool("unifi_list_mc_lag_domains", {
59
- description: "List all MC-LAG (Multi-Chassis LAG) Domains at a site",
62
+ description: "List MC-LAG (Multi-Chassis Link Aggregation) Domains pairs of switches presenting as one for LAG redundancy. Returns: id, name, peers[], lags[] (LAGs spanning the domain), metadata.origin.",
60
63
  inputSchema: {
61
64
  siteId: zod_1.z.string().describe("Site ID"),
62
65
  offset: zod_1.z
@@ -77,35 +80,37 @@ function registerSwitchingTools(server, client) {
77
80
  .optional()
78
81
  .describe("Filter expression"),
79
82
  },
83
+ outputSchema: output_schemas_js_1.listMcLagDomainsOutputSchema,
80
84
  annotations: safety_js_1.READ_ONLY,
81
85
  }, async ({ siteId, offset, limit, filter }) => {
82
86
  try {
83
87
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
84
88
  const data = await client.get(`/sites/${siteId}/switching/mc-lag-domains${query}`);
85
- return (0, responses_js_1.formatSuccess)(data);
89
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
86
90
  }
87
91
  catch (err) {
88
92
  return (0, responses_js_1.formatError)(err);
89
93
  }
90
94
  });
91
95
  server.registerTool("unifi_get_mc_lag_domain", {
92
- description: "Get details of a specific MC-LAG (Multi-Chassis LAG) Domain",
96
+ description: "Get full details of an MC-LAG Domain (peer switches and member LAGs).",
93
97
  inputSchema: {
94
98
  siteId: zod_1.z.string().describe("Site ID"),
95
99
  mcLagDomainId: zod_1.z.string().describe("MC-LAG Domain ID"),
96
100
  },
101
+ outputSchema: output_schemas_js_1.mcLagDomainOutputSchema,
97
102
  annotations: safety_js_1.READ_ONLY,
98
103
  }, async ({ siteId, mcLagDomainId }) => {
99
104
  try {
100
105
  const data = await client.get(`/sites/${siteId}/switching/mc-lag-domains/${mcLagDomainId}`);
101
- return (0, responses_js_1.formatSuccess)(data);
106
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
102
107
  }
103
108
  catch (err) {
104
109
  return (0, responses_js_1.formatError)(err);
105
110
  }
106
111
  });
107
112
  server.registerTool("unifi_list_lags", {
108
- description: "List all LAGs (Link Aggregation Groups) at a site",
113
+ description: "List LAGs (Link Aggregation Groups — bonded switch ports) at a site. Returns: id, type (LOCAL/SWITCH_STACK/MULTI_CHASSIS), members[], metadata.origin.",
109
114
  inputSchema: {
110
115
  siteId: zod_1.z.string().describe("Site ID"),
111
116
  offset: zod_1.z
@@ -126,28 +131,30 @@ function registerSwitchingTools(server, client) {
126
131
  .optional()
127
132
  .describe("Filter expression"),
128
133
  },
134
+ outputSchema: output_schemas_js_1.listLagsOutputSchema,
129
135
  annotations: safety_js_1.READ_ONLY,
130
136
  }, async ({ siteId, offset, limit, filter }) => {
131
137
  try {
132
138
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
133
139
  const data = await client.get(`/sites/${siteId}/switching/lags${query}`);
134
- return (0, responses_js_1.formatSuccess)(data);
140
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
135
141
  }
136
142
  catch (err) {
137
143
  return (0, responses_js_1.formatError)(err);
138
144
  }
139
145
  });
140
146
  server.registerTool("unifi_get_lag", {
141
- description: "Get details of a specific LAG (Link Aggregation Group)",
147
+ description: "Get full details of a LAG including its type (LOCAL/SWITCH_STACK/MULTI_CHASSIS) and member ports.",
142
148
  inputSchema: {
143
149
  siteId: zod_1.z.string().describe("Site ID"),
144
150
  lagId: zod_1.z.string().describe("LAG ID"),
145
151
  },
152
+ outputSchema: output_schemas_js_1.lagOutputSchema,
146
153
  annotations: safety_js_1.READ_ONLY,
147
154
  }, async ({ siteId, lagId }) => {
148
155
  try {
149
156
  const data = await client.get(`/sites/${siteId}/switching/lags/${lagId}`);
150
- return (0, responses_js_1.formatSuccess)(data);
157
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
151
158
  }
152
159
  catch (err) {
153
160
  return (0, responses_js_1.formatError)(err);
@@ -3,15 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerSystemTools = registerSystemTools;
4
4
  const responses_js_1 = require("../utils/responses.js");
5
5
  const safety_js_1 = require("../utils/safety.js");
6
+ const output_schemas_js_1 = require("../utils/output-schemas.js");
6
7
  function registerSystemTools(server, client) {
7
8
  server.registerTool("unifi_get_info", {
8
- description: "Get application information including version and whether it's a UniFi OS Console",
9
+ description: "Get UniFi Network application info. Returns: applicationVersion. NOTE: verified against 10.4.55 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.",
9
10
  inputSchema: {},
11
+ outputSchema: output_schemas_js_1.getInfoOutputSchema,
10
12
  annotations: safety_js_1.READ_ONLY,
11
13
  }, async () => {
12
14
  try {
13
15
  const data = await client.get("/info");
14
- return (0, responses_js_1.formatSuccess)(data);
16
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
15
17
  }
16
18
  catch (err) {
17
19
  return (0, responses_js_1.formatError)(err);
@@ -5,9 +5,10 @@ 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 output_schemas_js_1 = require("../utils/output-schemas.js");
8
9
  function registerTrafficMatchingTools(server, client, readOnly = false) {
9
10
  server.registerTool("unifi_list_traffic_matching_lists", {
10
- description: "List all traffic matching lists at a site (port groups, IP groups)",
11
+ description: "List traffic matching lists at a site — named collections of ports or IPs reused in firewall/ACL rules. Returns: id, type (PORTS/IPV4_ADDRESSES/IPV6_ADDRESSES), name, items[]. Use for: finding the matching-list ID to reference from a firewall policy.",
11
12
  inputSchema: {
12
13
  siteId: zod_1.z.string().describe("Site ID"),
13
14
  offset: zod_1.z
@@ -28,28 +29,30 @@ function registerTrafficMatchingTools(server, client, readOnly = false) {
28
29
  .optional()
29
30
  .describe("Filter expression"),
30
31
  },
32
+ outputSchema: output_schemas_js_1.listTrafficMatchingListsOutputSchema,
31
33
  annotations: safety_js_1.READ_ONLY,
32
34
  }, async ({ siteId, offset, limit, filter }) => {
33
35
  try {
34
36
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
35
37
  const data = await client.get(`/sites/${siteId}/traffic-matching-lists${query}`);
36
- return (0, responses_js_1.formatSuccess)(data);
38
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
37
39
  }
38
40
  catch (err) {
39
41
  return (0, responses_js_1.formatError)(err);
40
42
  }
41
43
  });
42
44
  server.registerTool("unifi_get_traffic_matching_list", {
43
- description: "Get a specific traffic matching list by ID",
45
+ description: "Get a specific traffic matching list by ID (full items array).",
44
46
  inputSchema: {
45
47
  siteId: zod_1.z.string().describe("Site ID"),
46
48
  trafficMatchingListId: zod_1.z.string().describe("Traffic matching list ID"),
47
49
  },
50
+ outputSchema: output_schemas_js_1.trafficMatchingListOutputSchema,
48
51
  annotations: safety_js_1.READ_ONLY,
49
52
  }, async ({ siteId, trafficMatchingListId }) => {
50
53
  try {
51
54
  const data = await client.get(`/sites/${siteId}/traffic-matching-lists/${trafficMatchingListId}`);
52
- return (0, responses_js_1.formatSuccess)(data);
55
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
53
56
  }
54
57
  catch (err) {
55
58
  return (0, responses_js_1.formatError)(err);
@@ -74,6 +77,7 @@ function registerTrafficMatchingTools(server, client, readOnly = false) {
74
77
  .optional()
75
78
  .describe("Preview this action without executing it"),
76
79
  },
80
+ outputSchema: output_schemas_js_1.trafficMatchingListOutputSchema,
77
81
  annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
78
82
  }, async ({ siteId, type, name, items, dryRun }) => {
79
83
  try {
@@ -81,7 +85,7 @@ function registerTrafficMatchingTools(server, client, readOnly = false) {
81
85
  if (dryRun)
82
86
  return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/traffic-matching-lists`, body);
83
87
  const data = await client.post(`/sites/${siteId}/traffic-matching-lists`, body);
84
- return (0, responses_js_1.formatSuccess)(data);
88
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
85
89
  }
86
90
  catch (err) {
87
91
  return (0, responses_js_1.formatError)(err);
@@ -102,6 +106,7 @@ function registerTrafficMatchingTools(server, client, readOnly = false) {
102
106
  .optional()
103
107
  .describe("Preview this action without executing it"),
104
108
  },
109
+ outputSchema: output_schemas_js_1.trafficMatchingListOutputSchema,
105
110
  annotations: safety_js_1.WRITE,
106
111
  }, async ({ siteId, trafficMatchingListId, type, name, items, dryRun }) => {
107
112
  try {
@@ -109,7 +114,7 @@ function registerTrafficMatchingTools(server, client, readOnly = false) {
109
114
  if (dryRun)
110
115
  return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/traffic-matching-lists/${trafficMatchingListId}`, body);
111
116
  const data = await client.put(`/sites/${siteId}/traffic-matching-lists/${trafficMatchingListId}`, body);
112
- return (0, responses_js_1.formatSuccess)(data);
117
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
113
118
  }
114
119
  catch (err) {
115
120
  return (0, responses_js_1.formatError)(err);
@@ -5,9 +5,10 @@ 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 output_schemas_js_1 = require("../utils/output-schemas.js");
8
9
  function registerWifiTools(server, client, readOnly = false) {
9
10
  server.registerTool("unifi_list_wifi", {
10
- description: "List all WiFi broadcasts (SSIDs) at a site",
11
+ description: "List all WiFi broadcasts (SSIDs) at a site. Returns: id, name (SSID), enabled, type (STANDARD/IOT_OPTIMIZED), broadcastingFrequenciesGHz (2.4/5/6), securityConfiguration, hideName, bandSteeringEnabled, mloEnabled, network reference, broadcastingDeviceFilter (which APs broadcast it). Use for: SSID inventory. For per-AP radio state (channel, txPower), use unifi_get_device on the AP.",
11
12
  inputSchema: {
12
13
  siteId: zod_1.z.string().describe("Site ID"),
13
14
  offset: zod_1.z
@@ -28,28 +29,30 @@ function registerWifiTools(server, client, readOnly = false) {
28
29
  .optional()
29
30
  .describe("Filter expression"),
30
31
  },
32
+ outputSchema: output_schemas_js_1.listWifiOutputSchema,
31
33
  annotations: safety_js_1.READ_ONLY,
32
34
  }, async ({ siteId, offset, limit, filter }) => {
33
35
  try {
34
36
  const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
35
37
  const data = await client.get(`/sites/${siteId}/wifi/broadcasts${query}`);
36
- return (0, responses_js_1.formatSuccess)(data);
38
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
37
39
  }
38
40
  catch (err) {
39
41
  return (0, responses_js_1.formatError)(err);
40
42
  }
41
43
  });
42
44
  server.registerTool("unifi_get_wifi", {
43
- description: "Get a specific WiFi network by ID",
45
+ description: "Get full configuration for a WiFi broadcast (SSID), including all optional fields not always returned by list: clientFilteringPolicy, blackoutScheduleConfiguration, hotspotConfiguration, mdnsProxyConfiguration, handoffSuggestionsConfiguration, dtimPeriodByFrequencyGHzOverride, etc.",
44
46
  inputSchema: {
45
47
  siteId: zod_1.z.string().describe("Site ID"),
46
48
  wifiBroadcastId: zod_1.z.string().describe("WiFi Broadcast ID"),
47
49
  },
50
+ outputSchema: output_schemas_js_1.getWifiOutputSchema,
48
51
  annotations: safety_js_1.READ_ONLY,
49
52
  }, async ({ siteId, wifiBroadcastId }) => {
50
53
  try {
51
54
  const data = await client.get(`/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`);
52
- return (0, responses_js_1.formatSuccess)(data);
55
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
53
56
  }
54
57
  catch (err) {
55
58
  return (0, responses_js_1.formatError)(err);
@@ -150,6 +153,7 @@ function registerWifiTools(server, client, readOnly = false) {
150
153
  .optional()
151
154
  .describe("Preview this action without executing it"),
152
155
  },
156
+ outputSchema: output_schemas_js_1.getWifiOutputSchema,
153
157
  annotations: safety_js_1.WRITE_NOT_IDEMPOTENT,
154
158
  }, async ({ siteId, name, enabled, type, broadcastingFrequenciesGHz, securityConfiguration, multicastToUnicastConversionEnabled, clientIsolationEnabled, hideName, uapsdEnabled, arpProxyEnabled, bssTransitionEnabled, advertiseDeviceName, channel2gLockedTo6, dtimPeriod2gLockedTo3, network, broadcastingDeviceFilter, mdnsProxyConfiguration, multicastFilteringPolicy, basicDataRateKbpsByFrequencyGHz, clientFilteringPolicy, blackoutScheduleConfiguration, hotspotConfiguration, mloEnabled, bandSteeringEnabled, dtimPeriodByFrequencyGHzOverride, dnsAssistanceConfiguration, handoffSuggestionsConfiguration, dryRun, }) => {
155
159
  try {
@@ -198,7 +202,7 @@ function registerWifiTools(server, client, readOnly = false) {
198
202
  if (dryRun)
199
203
  return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/wifi/broadcasts`, body);
200
204
  const data = await client.post(`/sites/${siteId}/wifi/broadcasts`, body);
201
- return (0, responses_js_1.formatSuccess)(data);
205
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
202
206
  }
203
207
  catch (err) {
204
208
  return (0, responses_js_1.formatError)(err);
@@ -313,6 +317,7 @@ function registerWifiTools(server, client, readOnly = false) {
313
317
  .optional()
314
318
  .describe("Preview this action without executing it"),
315
319
  },
320
+ outputSchema: output_schemas_js_1.getWifiOutputSchema,
316
321
  annotations: safety_js_1.WRITE,
317
322
  }, async ({ siteId, wifiBroadcastId, name, enabled, type, broadcastingFrequenciesGHz, securityConfiguration, multicastToUnicastConversionEnabled, clientIsolationEnabled, hideName, uapsdEnabled, arpProxyEnabled, bssTransitionEnabled, advertiseDeviceName, channel2gLockedTo6, dtimPeriod2gLockedTo3, network, broadcastingDeviceFilter, mdnsProxyConfiguration, multicastFilteringPolicy, basicDataRateKbpsByFrequencyGHz, clientFilteringPolicy, blackoutScheduleConfiguration, hotspotConfiguration, mloEnabled, bandSteeringEnabled, dtimPeriodByFrequencyGHzOverride, dnsAssistanceConfiguration, handoffSuggestionsConfiguration, dryRun, }) => {
318
323
  try {
@@ -375,7 +380,7 @@ function registerWifiTools(server, client, readOnly = false) {
375
380
  if (dryRun)
376
381
  return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`, body);
377
382
  const data = await client.put(`/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`, body);
378
- return (0, responses_js_1.formatSuccess)(data);
383
+ return (0, responses_js_1.formatSuccess)(data, { structured: true });
379
384
  }
380
385
  catch (err) {
381
386
  return (0, responses_js_1.formatError)(err);