@owine/unifi-network-mcp 0.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -19
- package/dist/config.js +2 -2
- package/dist/tools/devices.js +4 -4
- package/dist/tools/dns-policies.js +8 -8
- package/dist/tools/firewall.js +21 -0
- package/dist/tools/supporting.js +4 -4
- package/dist/tools/wifi.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,15 +15,7 @@ An MCP (Model Context Protocol) server that exposes the UniFi Network Integratio
|
|
|
15
15
|
Add to Claude Code with a single command — no clone or build needed:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
claude mcp add-json unifi-network '{
|
|
19
|
-
"command": "npx",
|
|
20
|
-
"args": ["-y", "@owine/unifi-network-mcp"],
|
|
21
|
-
"env": {
|
|
22
|
-
"UNIFI_NETWORK_HOST": "192.168.1.1",
|
|
23
|
-
"UNIFI_NETWORK_API_KEY": "your-api-key",
|
|
24
|
-
"UNIFI_NETWORK_VERIFY_SSL": "false"
|
|
25
|
-
}
|
|
26
|
-
}' -s user
|
|
18
|
+
claude mcp add-json unifi-network '{"command":"npx","args":["-y","@owine/unifi-network-mcp@latest"],"env":{"UNIFI_NETWORK_HOST":"192.168.1.1","UNIFI_NETWORK_API_KEY":"your-api-key","UNIFI_NETWORK_VERIFY_SSL":"false"}}' -s user
|
|
27
19
|
```
|
|
28
20
|
|
|
29
21
|
Use `-s user` for global availability across all projects, or `-s project` for the current project only.
|
|
@@ -42,15 +34,7 @@ npm run build
|
|
|
42
34
|
Then add to Claude Code:
|
|
43
35
|
|
|
44
36
|
```bash
|
|
45
|
-
claude mcp add-json unifi-network '{
|
|
46
|
-
"command": "node",
|
|
47
|
-
"args": ["/path/to/unifi-network-mcp/dist/index.js"],
|
|
48
|
-
"env": {
|
|
49
|
-
"UNIFI_NETWORK_HOST": "192.168.1.1",
|
|
50
|
-
"UNIFI_NETWORK_API_KEY": "your-api-key",
|
|
51
|
-
"UNIFI_NETWORK_VERIFY_SSL": "false"
|
|
52
|
-
}
|
|
53
|
-
}' -s user
|
|
37
|
+
claude mcp add-json unifi-network '{"command":"node","args":["/path/to/unifi-network-mcp/dist/index.js"],"env":{"UNIFI_NETWORK_HOST":"192.168.1.1","UNIFI_NETWORK_API_KEY":"your-api-key","UNIFI_NETWORK_VERIFY_SSL":"false"}}' -s user
|
|
54
38
|
```
|
|
55
39
|
|
|
56
40
|
### Environment Variables
|
|
@@ -71,7 +55,7 @@ Alternatively, add to your `~/.claude.json` under the top-level `"mcpServers"` k
|
|
|
71
55
|
"mcpServers": {
|
|
72
56
|
"unifi-network": {
|
|
73
57
|
"command": "npx",
|
|
74
|
-
"args": ["-y", "@owine/unifi-network-mcp"],
|
|
58
|
+
"args": ["-y", "@owine/unifi-network-mcp@latest"],
|
|
75
59
|
"env": {
|
|
76
60
|
"UNIFI_NETWORK_HOST": "192.168.1.1",
|
|
77
61
|
"UNIFI_NETWORK_API_KEY": "your-api-key",
|
package/dist/config.js
CHANGED
|
@@ -11,7 +11,7 @@ const configSchema = zod_1.z.object({
|
|
|
11
11
|
.describe("Verify SSL certificates"),
|
|
12
12
|
readOnly: zod_1.z
|
|
13
13
|
.boolean()
|
|
14
|
-
.default(
|
|
14
|
+
.default(true)
|
|
15
15
|
.describe("When true, only read-only tools are registered"),
|
|
16
16
|
});
|
|
17
17
|
function loadConfig() {
|
|
@@ -19,7 +19,7 @@ function loadConfig() {
|
|
|
19
19
|
host: process.env.UNIFI_NETWORK_HOST,
|
|
20
20
|
apiKey: process.env.UNIFI_NETWORK_API_KEY,
|
|
21
21
|
verifySsl: process.env.UNIFI_NETWORK_VERIFY_SSL?.toLowerCase() !== "false",
|
|
22
|
-
readOnly: process.env.UNIFI_NETWORK_READ_ONLY?.toLowerCase()
|
|
22
|
+
readOnly: process.env.UNIFI_NETWORK_READ_ONLY?.toLowerCase() !== "false",
|
|
23
23
|
});
|
|
24
24
|
if (!result.success) {
|
|
25
25
|
console.error("Configuration error:", result.error.format());
|
package/dist/tools/devices.js
CHANGED
|
@@ -49,7 +49,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
49
49
|
deviceId: zod_1.z.string().describe("Device ID"),
|
|
50
50
|
}, safety_js_1.READ_ONLY, async ({ siteId, deviceId }) => {
|
|
51
51
|
try {
|
|
52
|
-
const data = await client.get(`/sites/${siteId}/devices/${deviceId}/statistics`);
|
|
52
|
+
const data = await client.get(`/sites/${siteId}/devices/${deviceId}/statistics/latest`);
|
|
53
53
|
return (0, responses_js_1.formatSuccess)(data);
|
|
54
54
|
}
|
|
55
55
|
catch (err) {
|
|
@@ -74,7 +74,7 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
74
74
|
}, safety_js_1.READ_ONLY, async ({ offset, limit, filter }) => {
|
|
75
75
|
try {
|
|
76
76
|
const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
|
|
77
|
-
const data = await client.get(`/devices
|
|
77
|
+
const data = await client.get(`/pending-devices${query}`);
|
|
78
78
|
return (0, responses_js_1.formatSuccess)(data);
|
|
79
79
|
}
|
|
80
80
|
catch (err) {
|
|
@@ -145,9 +145,9 @@ function registerDeviceTools(server, client, readOnly = false) {
|
|
|
145
145
|
}, safety_js_1.WRITE, async ({ siteId, deviceId, portIdx, dryRun }) => {
|
|
146
146
|
const body = { action: "POWER_CYCLE" };
|
|
147
147
|
if (dryRun)
|
|
148
|
-
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/devices/${deviceId}/ports/${portIdx}/actions`, body);
|
|
148
|
+
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/devices/${deviceId}/interfaces/ports/${portIdx}/actions`, body);
|
|
149
149
|
try {
|
|
150
|
-
const data = await client.post(`/sites/${siteId}/devices/${deviceId}/ports/${portIdx}/actions`, body);
|
|
150
|
+
const data = await client.post(`/sites/${siteId}/devices/${deviceId}/interfaces/ports/${portIdx}/actions`, body);
|
|
151
151
|
return (0, responses_js_1.formatSuccess)(data);
|
|
152
152
|
}
|
|
153
153
|
catch (err) {
|
|
@@ -28,7 +28,7 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
28
28
|
}, safety_js_1.READ_ONLY, async ({ siteId, offset, limit, filter }) => {
|
|
29
29
|
try {
|
|
30
30
|
const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
|
|
31
|
-
const data = await client.get(`/sites/${siteId}/dns
|
|
31
|
+
const data = await client.get(`/sites/${siteId}/dns/policies${query}`);
|
|
32
32
|
return (0, responses_js_1.formatSuccess)(data);
|
|
33
33
|
}
|
|
34
34
|
catch (err) {
|
|
@@ -40,7 +40,7 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
40
40
|
dnsPolicyId: zod_1.z.string().describe("DNS policy ID"),
|
|
41
41
|
}, safety_js_1.READ_ONLY, async ({ siteId, dnsPolicyId }) => {
|
|
42
42
|
try {
|
|
43
|
-
const data = await client.get(`/sites/${siteId}/dns
|
|
43
|
+
const data = await client.get(`/sites/${siteId}/dns/policies/${dnsPolicyId}`);
|
|
44
44
|
return (0, responses_js_1.formatSuccess)(data);
|
|
45
45
|
}
|
|
46
46
|
catch (err) {
|
|
@@ -61,8 +61,8 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
61
61
|
}, safety_js_1.WRITE_NOT_IDEMPOTENT, async ({ siteId, policy, dryRun }) => {
|
|
62
62
|
try {
|
|
63
63
|
if (dryRun)
|
|
64
|
-
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/dns
|
|
65
|
-
const data = await client.post(`/sites/${siteId}/dns
|
|
64
|
+
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/dns/policies`, policy);
|
|
65
|
+
const data = await client.post(`/sites/${siteId}/dns/policies`, policy);
|
|
66
66
|
return (0, responses_js_1.formatSuccess)(data);
|
|
67
67
|
}
|
|
68
68
|
catch (err) {
|
|
@@ -82,8 +82,8 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
82
82
|
}, safety_js_1.WRITE, async ({ siteId, dnsPolicyId, policy, dryRun }) => {
|
|
83
83
|
try {
|
|
84
84
|
if (dryRun)
|
|
85
|
-
return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/dns
|
|
86
|
-
const data = await client.put(`/sites/${siteId}/dns
|
|
85
|
+
return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/dns/policies/${dnsPolicyId}`, policy);
|
|
86
|
+
const data = await client.put(`/sites/${siteId}/dns/policies/${dnsPolicyId}`, policy);
|
|
87
87
|
return (0, responses_js_1.formatSuccess)(data);
|
|
88
88
|
}
|
|
89
89
|
catch (err) {
|
|
@@ -107,8 +107,8 @@ function registerDnsPolicyTools(server, client, readOnly = false) {
|
|
|
107
107
|
return guard;
|
|
108
108
|
try {
|
|
109
109
|
if (dryRun)
|
|
110
|
-
return (0, safety_js_1.formatDryRun)("DELETE", `/sites/${siteId}/dns
|
|
111
|
-
const data = await client.delete(`/sites/${siteId}/dns
|
|
110
|
+
return (0, safety_js_1.formatDryRun)("DELETE", `/sites/${siteId}/dns/policies/${dnsPolicyId}`, {});
|
|
111
|
+
const data = await client.delete(`/sites/${siteId}/dns/policies/${dnsPolicyId}`);
|
|
112
112
|
return (0, responses_js_1.formatSuccess)(data);
|
|
113
113
|
}
|
|
114
114
|
catch (err) {
|
package/dist/tools/firewall.js
CHANGED
|
@@ -217,6 +217,27 @@ function registerFirewallTools(server, client, readOnly = false) {
|
|
|
217
217
|
return (0, responses_js_1.formatError)(err);
|
|
218
218
|
}
|
|
219
219
|
});
|
|
220
|
+
server.tool("unifi_patch_firewall_policy", "Partially update a firewall policy (e.g. toggle logging)", {
|
|
221
|
+
siteId: zod_1.z.string().describe("Site ID"),
|
|
222
|
+
firewallPolicyId: zod_1.z.string().describe("Firewall policy ID"),
|
|
223
|
+
policy: zod_1.z
|
|
224
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
225
|
+
.describe("Partial firewall policy fields to update (e.g. { loggingEnabled: true })"),
|
|
226
|
+
dryRun: zod_1.z
|
|
227
|
+
.boolean()
|
|
228
|
+
.optional()
|
|
229
|
+
.describe("Preview this action without executing it"),
|
|
230
|
+
}, safety_js_1.WRITE, async ({ siteId, firewallPolicyId, policy, dryRun }) => {
|
|
231
|
+
try {
|
|
232
|
+
if (dryRun)
|
|
233
|
+
return (0, safety_js_1.formatDryRun)("PATCH", `/sites/${siteId}/firewall/policies/${firewallPolicyId}`, policy);
|
|
234
|
+
const data = await client.patch(`/sites/${siteId}/firewall/policies/${firewallPolicyId}`, policy);
|
|
235
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
return (0, responses_js_1.formatError)(err);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
220
241
|
server.tool("unifi_delete_firewall_policy", "DESTRUCTIVE: Delete a firewall policy", {
|
|
221
242
|
siteId: zod_1.z.string().describe("Site ID"),
|
|
222
243
|
firewallPolicyId: zod_1.z.string().describe("Firewall policy ID"),
|
package/dist/tools/supporting.js
CHANGED
|
@@ -24,7 +24,7 @@ function registerSupportingTools(server, client) {
|
|
|
24
24
|
}, safety_js_1.READ_ONLY, async ({ siteId, offset, limit }) => {
|
|
25
25
|
try {
|
|
26
26
|
const query = (0, query_js_1.buildQuery)({ offset, limit });
|
|
27
|
-
const data = await client.get(`/sites/${siteId}/
|
|
27
|
+
const data = await client.get(`/sites/${siteId}/wans${query}`);
|
|
28
28
|
return (0, responses_js_1.formatSuccess)(data);
|
|
29
29
|
}
|
|
30
30
|
catch (err) {
|
|
@@ -53,7 +53,7 @@ function registerSupportingTools(server, client) {
|
|
|
53
53
|
}, safety_js_1.READ_ONLY, async ({ siteId, offset, limit, filter }) => {
|
|
54
54
|
try {
|
|
55
55
|
const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
|
|
56
|
-
const data = await client.get(`/sites/${siteId}/vpn-tunnels${query}`);
|
|
56
|
+
const data = await client.get(`/sites/${siteId}/vpn/site-to-site-tunnels${query}`);
|
|
57
57
|
return (0, responses_js_1.formatSuccess)(data);
|
|
58
58
|
}
|
|
59
59
|
catch (err) {
|
|
@@ -82,7 +82,7 @@ function registerSupportingTools(server, client) {
|
|
|
82
82
|
}, safety_js_1.READ_ONLY, async ({ siteId, offset, limit, filter }) => {
|
|
83
83
|
try {
|
|
84
84
|
const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
|
|
85
|
-
const data = await client.get(`/sites/${siteId}/vpn
|
|
85
|
+
const data = await client.get(`/sites/${siteId}/vpn/servers${query}`);
|
|
86
86
|
return (0, responses_js_1.formatSuccess)(data);
|
|
87
87
|
}
|
|
88
88
|
catch (err) {
|
|
@@ -111,7 +111,7 @@ function registerSupportingTools(server, client) {
|
|
|
111
111
|
}, safety_js_1.READ_ONLY, async ({ siteId, offset, limit, filter }) => {
|
|
112
112
|
try {
|
|
113
113
|
const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
|
|
114
|
-
const data = await client.get(`/sites/${siteId}/radius
|
|
114
|
+
const data = await client.get(`/sites/${siteId}/radius/profiles${query}`);
|
|
115
115
|
return (0, responses_js_1.formatSuccess)(data);
|
|
116
116
|
}
|
|
117
117
|
catch (err) {
|
package/dist/tools/wifi.js
CHANGED
|
@@ -28,7 +28,7 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
28
28
|
}, safety_js_1.READ_ONLY, async ({ siteId, offset, limit, filter }) => {
|
|
29
29
|
try {
|
|
30
30
|
const query = (0, query_js_1.buildQuery)({ offset, limit, filter });
|
|
31
|
-
const data = await client.get(`/sites/${siteId}/wifi${query}`);
|
|
31
|
+
const data = await client.get(`/sites/${siteId}/wifi/broadcasts${query}`);
|
|
32
32
|
return (0, responses_js_1.formatSuccess)(data);
|
|
33
33
|
}
|
|
34
34
|
catch (err) {
|
|
@@ -40,7 +40,7 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
40
40
|
wifiBroadcastId: zod_1.z.string().describe("WiFi Broadcast ID"),
|
|
41
41
|
}, safety_js_1.READ_ONLY, async ({ siteId, wifiBroadcastId }) => {
|
|
42
42
|
try {
|
|
43
|
-
const data = await client.get(`/sites/${siteId}/wifi/${wifiBroadcastId}`);
|
|
43
|
+
const data = await client.get(`/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`);
|
|
44
44
|
return (0, responses_js_1.formatSuccess)(data);
|
|
45
45
|
}
|
|
46
46
|
catch (err) {
|
|
@@ -70,8 +70,8 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
70
70
|
broadcastingFrequenciesGHz: broadcastingFrequenciesGHz.map(Number),
|
|
71
71
|
};
|
|
72
72
|
if (dryRun)
|
|
73
|
-
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/wifi`, body);
|
|
74
|
-
const data = await client.post(`/sites/${siteId}/wifi`, body);
|
|
73
|
+
return (0, safety_js_1.formatDryRun)("POST", `/sites/${siteId}/wifi/broadcasts`, body);
|
|
74
|
+
const data = await client.post(`/sites/${siteId}/wifi/broadcasts`, body);
|
|
75
75
|
return (0, responses_js_1.formatSuccess)(data);
|
|
76
76
|
}
|
|
77
77
|
catch (err) {
|
|
@@ -95,8 +95,8 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
95
95
|
if (enabled !== undefined)
|
|
96
96
|
body.enabled = enabled;
|
|
97
97
|
if (dryRun)
|
|
98
|
-
return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/wifi/${wifiBroadcastId}`, body);
|
|
99
|
-
const data = await client.put(`/sites/${siteId}/wifi/${wifiBroadcastId}`, body);
|
|
98
|
+
return (0, safety_js_1.formatDryRun)("PUT", `/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`, body);
|
|
99
|
+
const data = await client.put(`/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`, body);
|
|
100
100
|
return (0, responses_js_1.formatSuccess)(data);
|
|
101
101
|
}
|
|
102
102
|
catch (err) {
|
|
@@ -124,7 +124,7 @@ function registerWifiTools(server, client, readOnly = false) {
|
|
|
124
124
|
if (guard)
|
|
125
125
|
return guard;
|
|
126
126
|
try {
|
|
127
|
-
let path = `/sites/${siteId}/wifi/${wifiBroadcastId}`;
|
|
127
|
+
let path = `/sites/${siteId}/wifi/broadcasts/${wifiBroadcastId}`;
|
|
128
128
|
if (force) {
|
|
129
129
|
path += "?force=true";
|
|
130
130
|
}
|