unifi-network-mcp 0.2.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (57) hide show
  1. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/.well-known/mcp-server.json +8 -4
  2. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/PKG-INFO +133 -50
  3. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/README.md +132 -49
  4. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/pyproject.toml +13 -1
  5. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/bootstrap.py +8 -20
  6. unifi_network_mcp-0.3.1/src/config/config.yaml +120 -0
  7. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/jobs.py +1 -3
  8. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/main.py +73 -64
  9. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/client_manager.py +112 -40
  10. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/connection_manager.py +40 -121
  11. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/device_manager.py +9 -19
  12. unifi_network_mcp-0.3.1/src/managers/event_manager.py +182 -0
  13. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/firewall_manager.py +63 -186
  14. unifi_network_mcp-0.3.1/src/managers/hotspot_manager.py +196 -0
  15. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/network_manager.py +28 -81
  16. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/qos_manager.py +10 -25
  17. unifi_network_mcp-0.3.1/src/managers/routing_manager.py +232 -0
  18. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/stats_manager.py +24 -65
  19. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/managers/system_manager.py +50 -143
  20. unifi_network_mcp-0.3.1/src/managers/traffic_route_manager.py +191 -0
  21. unifi_network_mcp-0.3.1/src/managers/usergroup_manager.py +186 -0
  22. unifi_network_mcp-0.3.1/src/managers/vpn_manager.py +304 -0
  23. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/runtime.py +41 -6
  24. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/schemas.py +2 -4
  25. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tool_index.py +4 -8
  26. unifi_network_mcp-0.3.1/src/tools/clients.py +578 -0
  27. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/config.py +4 -9
  28. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/devices.py +149 -48
  29. unifi_network_mcp-0.3.1/src/tools/events.py +181 -0
  30. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/firewall.py +89 -138
  31. unifi_network_mcp-0.3.1/src/tools/hotspot.py +232 -0
  32. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/network.py +85 -100
  33. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/port_forwards.py +64 -125
  34. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/qos.py +79 -95
  35. unifi_network_mcp-0.3.1/src/tools/routing.py +332 -0
  36. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/stats.py +18 -55
  37. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/system.py +2 -4
  38. unifi_network_mcp-0.3.1/src/tools/traffic_routes.py +242 -0
  39. unifi_network_mcp-0.3.1/src/tools/usergroups.py +238 -0
  40. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools/vpn.py +14 -38
  41. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/tools_manifest.json +397 -34
  42. unifi_network_mcp-0.3.1/src/utils/confirmation.py +218 -0
  43. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/utils/diagnostics.py +15 -13
  44. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/utils/lazy_tool_loader.py +37 -7
  45. unifi_network_mcp-0.3.1/src/utils/meta_tools.py +350 -0
  46. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/utils/permissions.py +6 -4
  47. unifi_network_mcp-0.3.1/src/utils/tool_loader.py +103 -0
  48. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/validator_registry.py +22 -35
  49. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/src/validators.py +5 -8
  50. unifi_network_mcp-0.2.0/src/config/config.yaml +0 -74
  51. unifi_network_mcp-0.2.0/src/managers/vpn_manager.py +0 -206
  52. unifi_network_mcp-0.2.0/src/tools/clients.py +0 -342
  53. unifi_network_mcp-0.2.0/src/tools/traffic_routes.py +0 -705
  54. unifi_network_mcp-0.2.0/src/utils/meta_tools.py +0 -215
  55. unifi_network_mcp-0.2.0/src/utils/tool_loader.py +0 -37
  56. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/.gitignore +0 -0
  57. {unifi_network_mcp-0.2.0 → unifi_network_mcp-0.3.1}/LICENSE +0 -0
@@ -46,10 +46,14 @@
46
46
  "tool": "unifi_tool_index",
47
47
  "description": "Returns machine-readable list of all available tools with schemas"
48
48
  },
49
- "async_operations": {
50
- "start_tool": "unifi_async_start",
51
- "status_tool": "unifi_async_status",
52
- "description": "Background job execution for long-running operations"
49
+ "execution": {
50
+ "tool": "unifi_execute",
51
+ "description": "Execute any discovered tool synchronously"
52
+ },
53
+ "batch_operations": {
54
+ "start_tool": "unifi_batch",
55
+ "status_tool": "unifi_batch_status",
56
+ "description": "Parallel batch execution for bulk operations"
53
57
  }
54
58
  }
55
59
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unifi-network-mcp
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: Unifi Network MCP Server
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.13
@@ -45,7 +45,7 @@ A self-hosted [Model Context Protocol](https://github.com/modelcontextprotocol)
45
45
  * [Overview](#overview)
46
46
  * [Context Optimization](#context-optimization)
47
47
  * [Tool Index](#tool-index)
48
- * [Async Operations](#async-operations)
48
+ * [Tool Execution](#tool-execution)
49
49
  * [Runtime Configuration](#runtime-configuration)
50
50
  * [Diagnostics (Advanced Logging)](#diagnostics-advanced-logging)
51
51
  * [Developer Console (Local Tool Tester)](#developer-console-local-tool-tester)
@@ -62,6 +62,7 @@ A self-hosted [Model Context Protocol](https://github.com/modelcontextprotocol)
62
62
 
63
63
  * Full catalog of UniFi controller operations – firewall, traffic-routes, port-forwards, QoS, VPN, WLANs, stats, devices, clients **and more**.
64
64
  * All mutating tools require `confirm=true` so nothing can change your network by accident.
65
+ * **Workflow automation friendly** – set `UNIFI_AUTO_CONFIRM=true` to skip confirmation prompts (ideal for n8n, Make, Zapier).
65
66
  * Works over **stdio** (FastMCP). Optional SSE HTTP endpoint can be enabled via config.
66
67
  * **Code execution mode** with tool index, async operations, and TypeScript examples.
67
68
  * One-liner launch via the console-script **`unifi-network-mcp`**.
@@ -267,73 +268,73 @@ The server exposes a special `unifi_tool_index` tool that returns a complete lis
267
268
  - Dynamic client configuration
268
269
  - IDE autocomplete support
269
270
 
270
- ### Async Operations
271
+ ### Tool Execution
271
272
 
272
- For long-running operations (device upgrades, bulk configuration changes), the server provides async job execution:
273
+ The server provides two execution modes for discovered tools:
273
274
 
274
- **Start a background job:**
275
+ **Single Tool Execution (synchronous):**
275
276
  ```json
276
277
  {
277
- "name": "unifi_async_start",
278
+ "name": "unifi_execute",
278
279
  "arguments": {
279
- "tool": "unifi_upgrade_device",
280
- "arguments": {
281
- "mac_address": "aa:bb:cc:dd:ee:ff",
282
- "confirm": true
283
- }
280
+ "tool": "unifi_list_clients",
281
+ "arguments": {}
284
282
  }
285
283
  }
286
284
  ```
287
285
 
288
- **Response:**
289
- ```json
290
- {
291
- "jobId": "af33b233cbdc860c"
292
- }
293
- ```
286
+ **Batch Execution (parallel, async):**
287
+
288
+ For bulk operations or long-running tasks, use batch mode:
294
289
 
295
- **Check job status:**
296
290
  ```json
297
291
  {
298
- "name": "unifi_async_status",
292
+ "name": "unifi_batch",
299
293
  "arguments": {
300
- "jobId": "af33b233cbdc860c"
294
+ "operations": [
295
+ {"tool": "unifi_get_client_details", "arguments": {"mac": "aa:bb:cc:dd:ee:ff"}},
296
+ {"tool": "unifi_get_client_details", "arguments": {"mac": "11:22:33:44:55:66"}}
297
+ ]
301
298
  }
302
299
  }
303
300
  ```
304
301
 
305
- **Response (running):**
302
+ **Response:**
306
303
  ```json
307
304
  {
308
- "status": "running",
309
- "started": 1234567890.123
305
+ "jobs": [
306
+ {"index": 0, "tool": "unifi_get_client_details", "jobId": "af33b233cbdc860c"},
307
+ {"index": 1, "tool": "unifi_get_client_details", "jobId": "bf44c344dcde971d"}
308
+ ],
309
+ "message": "Started 2 operation(s). Use unifi_batch_status to check progress."
310
310
  }
311
311
  ```
312
312
 
313
- **Response (completed):**
313
+ **Check batch status:**
314
314
  ```json
315
315
  {
316
- "status": "done",
317
- "result": {...},
318
- "started": 1234567890.123,
319
- "finished": 1234567891.456
316
+ "name": "unifi_batch_status",
317
+ "arguments": {
318
+ "jobIds": ["af33b233cbdc860c", "bf44c344dcde971d"]
319
+ }
320
320
  }
321
321
  ```
322
322
 
323
- **Response (error):**
323
+ **Response:**
324
324
  ```json
325
325
  {
326
- "status": "error",
327
- "error": "Device not found",
328
- "started": 1234567890.123,
329
- "finished": 1234567891.456
326
+ "jobs": [
327
+ {"jobId": "af33b233cbdc860c", "status": "done", "result": {...}},
328
+ {"jobId": "bf44c344dcde971d", "status": "done", "result": {...}}
329
+ ]
330
330
  }
331
331
  ```
332
332
 
333
333
  **Notes:**
334
+ - Use `unifi_execute` for single operations (returns result directly)
335
+ - Use `unifi_batch` + `unifi_batch_status` for parallel/bulk operations
334
336
  - Jobs are stored in-memory only (no persistence)
335
337
  - Job IDs are unique per server session
336
- - Failed jobs capture exception details in the `error` field
337
338
 
338
339
  ### Using with Claude Desktop
339
340
 
@@ -360,22 +361,32 @@ Practical examples showing programmatic usage:
360
361
  ```python
361
362
  from mcp import ClientSession, stdio_client
362
363
 
363
- # Query tool index
364
- result = await session.call_tool("unifi_tool_index", {})
364
+ # Discover tools
365
+ tools = await session.call_tool("unifi_tool_index", {})
366
+
367
+ # Execute a single tool (returns result directly)
368
+ result = await session.call_tool("unifi_execute", {
369
+ "tool": "unifi_list_clients",
370
+ "arguments": {}
371
+ })
365
372
 
366
- # Use async jobs for long operations
367
- job = await session.call_tool("unifi_async_start", {
368
- "tool": "unifi_upgrade_device",
369
- "arguments": {"mac_address": "...", "confirm": True}
373
+ # Batch execution for parallel operations
374
+ batch = await session.call_tool("unifi_batch", {
375
+ "operations": [
376
+ {"tool": "unifi_get_client_details", "arguments": {"mac": "..."}},
377
+ {"tool": "unifi_get_device_details", "arguments": {"mac": "..."}}
378
+ ]
370
379
  })
371
380
 
372
- # Check job status
373
- status = await session.call_tool("unifi_async_status", {"jobId": job["jobId"]})
381
+ # Check batch status
382
+ status = await session.call_tool("unifi_batch_status", {
383
+ "jobIds": [j["jobId"] for j in batch["jobs"]]
384
+ })
374
385
  ```
375
386
 
376
387
  **Three complete examples:**
377
388
  - `query_tool_index.py` - Discover available tools
378
- - `use_async_jobs.py` - Background job management
389
+ - `use_async_jobs.py` - Batch operations and status checking
379
390
  - `programmatic_client.py` - Build custom Python clients
380
391
 
381
392
  See [`examples/python/README.md`](examples/python/README.md) for complete examples.
@@ -392,15 +403,18 @@ The server advertises its capabilities via an MCP identity file at [`.well-known
392
403
  "capabilities": {
393
404
  "tools": true,
394
405
  "tool_index": true,
395
- "async_operations": true
406
+ "batch_operations": true
396
407
  },
397
408
  "features": {
398
409
  "tool_index": {
399
410
  "tool": "unifi_tool_index"
400
411
  },
401
- "async_operations": {
402
- "start_tool": "unifi_async_start",
403
- "status_tool": "unifi_async_status"
412
+ "execution": {
413
+ "tool": "unifi_execute"
414
+ },
415
+ "batch_operations": {
416
+ "start_tool": "unifi_batch",
417
+ "status_tool": "unifi_batch_status"
404
418
  }
405
419
  }
406
420
  }
@@ -520,6 +534,44 @@ The server merges settings from **environment variables**, an optional `.env` fi
520
534
  | `UNIFI_VERIFY_SSL` | Set to `false` if using self-signed certs |
521
535
  | `UNIFI_CONTROLLER_TYPE` | Controller API path type: `auto` (detect), `proxy` (UniFi OS), `direct` (standalone). Default `auto` |
522
536
  | `UNIFI_MCP_HTTP_ENABLED` | Set `true` to enable optional HTTP SSE server (default `false`) |
537
+ | `UNIFI_AUTO_CONFIRM` | Set `true` to auto-confirm all mutating operations (skips preview step). Ideal for workflow automation (n8n, Make, Zapier). Default `false` |
538
+ | `UNIFI_TOOL_REGISTRATION_MODE` | Tool loading mode: `lazy` (default), `eager`, or `meta_only`. See [Context Optimization](#context-optimization) |
539
+ | `UNIFI_ENABLED_CATEGORIES` | Comma-separated list of tool categories to load (eager mode). See table below |
540
+ | `UNIFI_ENABLED_TOOLS` | Comma-separated list of specific tool names to register (eager mode) |
541
+
542
+ ### Tool Categories (for UNIFI_ENABLED_CATEGORIES)
543
+
544
+ When using eager mode with category filtering, these are the valid category names:
545
+
546
+ | Category | Description | Example Tools |
547
+ |----------|-------------|---------------|
548
+ | `clients` | Client listing, blocking, guest auth | `unifi_list_clients`, `unifi_block_client` |
549
+ | `config` | Configuration management | - |
550
+ | `devices` | Device listing, reboot, locate, upgrade | `unifi_list_devices`, `unifi_reboot_device` |
551
+ | `events` | Events and alarms | `unifi_list_events`, `unifi_list_alarms` |
552
+ | `firewall` | Firewall rules and groups | `unifi_list_firewall_rules`, `unifi_create_firewall_rule` |
553
+ | `hotspot` | Vouchers for guest network | `unifi_list_vouchers`, `unifi_create_voucher` |
554
+ | `network` | Network/VLAN management | `unifi_list_networks`, `unifi_create_network` |
555
+ | `port_forwards` | Port forwarding rules | `unifi_list_port_forwards` |
556
+ | `qos` | QoS/traffic shaping rules | `unifi_list_qos_rules`, `unifi_create_qos_rule` |
557
+ | `routing` | Static routes (V1 API) | `unifi_list_routes`, `unifi_create_route` |
558
+ | `stats` | Statistics and metrics | `unifi_get_client_stats`, `unifi_get_device_stats` |
559
+ | `system` | System info, health, settings | `unifi_get_system_info`, `unifi_get_network_health` |
560
+ | `traffic_routes` | Policy-based routing (V2 API) | `unifi_list_traffic_routes` |
561
+ | `usergroups` | Bandwidth profiles/user groups | `unifi_list_usergroups`, `unifi_create_usergroup` |
562
+ | `vpn` | VPN servers and clients | `unifi_list_vpn_servers`, `unifi_list_vpn_clients` |
563
+
564
+ **Example usage:**
565
+ ```bash
566
+ # Load only client and system tools
567
+ export UNIFI_TOOL_REGISTRATION_MODE=eager
568
+ export UNIFI_ENABLED_CATEGORIES=clients,system
569
+
570
+ # Or load specific tools only
571
+ export UNIFI_ENABLED_TOOLS=unifi_list_clients,unifi_list_devices,unifi_get_system_info
572
+ ```
573
+
574
+ **Note:** Tools may also be filtered by the `permissions` section in config.yaml (e.g., `clients.update: false` blocks mutating client tools).
523
575
 
524
576
  ### Controller Type Detection
525
577
 
@@ -842,6 +894,37 @@ See [docs/permissions.md](docs/permissions.md) for complete documentation includ
842
894
  * `unifi_force_reconnect_client`
843
895
  * `unifi_authorize_guest`
844
896
  * `unifi_unauthorize_guest`
897
+ * `unifi_set_client_ip_settings`
898
+
899
+ ### Events & Alarms
900
+
901
+ * `unifi_list_events`
902
+ * `unifi_list_alarms`
903
+ * `unifi_archive_alarm`
904
+ * `unifi_archive_all_alarms`
905
+ * `unifi_get_event_types`
906
+
907
+ ### Routing (Static Routes)
908
+
909
+ * `unifi_list_routes`
910
+ * `unifi_get_route_details`
911
+ * `unifi_create_route`
912
+ * `unifi_update_route`
913
+ * `unifi_list_active_routes`
914
+
915
+ ### Hotspot (Vouchers)
916
+
917
+ * `unifi_list_vouchers`
918
+ * `unifi_get_voucher_details`
919
+ * `unifi_create_voucher`
920
+ * `unifi_revoke_voucher`
921
+
922
+ ### User Groups
923
+
924
+ * `unifi_list_usergroups`
925
+ * `unifi_get_usergroup_details`
926
+ * `unifi_create_usergroup`
927
+ * `unifi_update_usergroup`
845
928
 
846
929
  ### Statistics & Alerts
847
930
 
@@ -1018,9 +1101,9 @@ uv run python devtools/dev_console.py
1018
1101
 
1019
1102
  # You'll see a menu of all tools including:
1020
1103
  # - unifi_tool_index (list all tools with schemas)
1021
- # - unifi_async_start (start background jobs)
1022
- # - unifi_async_status (check job status)
1023
- # - All 60+ UniFi tools (clients, devices, networks, etc.)
1104
+ # - unifi_execute (run any discovered tool)
1105
+ # - unifi_batch / unifi_batch_status (parallel operations)
1106
+ # - All 80+ UniFi tools (clients, devices, networks, etc.)
1024
1107
  ```
1025
1108
 
1026
1109
  **4. Test with Python examples:**
@@ -29,7 +29,7 @@ A self-hosted [Model Context Protocol](https://github.com/modelcontextprotocol)
29
29
  * [Overview](#overview)
30
30
  * [Context Optimization](#context-optimization)
31
31
  * [Tool Index](#tool-index)
32
- * [Async Operations](#async-operations)
32
+ * [Tool Execution](#tool-execution)
33
33
  * [Runtime Configuration](#runtime-configuration)
34
34
  * [Diagnostics (Advanced Logging)](#diagnostics-advanced-logging)
35
35
  * [Developer Console (Local Tool Tester)](#developer-console-local-tool-tester)
@@ -46,6 +46,7 @@ A self-hosted [Model Context Protocol](https://github.com/modelcontextprotocol)
46
46
 
47
47
  * Full catalog of UniFi controller operations – firewall, traffic-routes, port-forwards, QoS, VPN, WLANs, stats, devices, clients **and more**.
48
48
  * All mutating tools require `confirm=true` so nothing can change your network by accident.
49
+ * **Workflow automation friendly** – set `UNIFI_AUTO_CONFIRM=true` to skip confirmation prompts (ideal for n8n, Make, Zapier).
49
50
  * Works over **stdio** (FastMCP). Optional SSE HTTP endpoint can be enabled via config.
50
51
  * **Code execution mode** with tool index, async operations, and TypeScript examples.
51
52
  * One-liner launch via the console-script **`unifi-network-mcp`**.
@@ -251,73 +252,73 @@ The server exposes a special `unifi_tool_index` tool that returns a complete lis
251
252
  - Dynamic client configuration
252
253
  - IDE autocomplete support
253
254
 
254
- ### Async Operations
255
+ ### Tool Execution
255
256
 
256
- For long-running operations (device upgrades, bulk configuration changes), the server provides async job execution:
257
+ The server provides two execution modes for discovered tools:
257
258
 
258
- **Start a background job:**
259
+ **Single Tool Execution (synchronous):**
259
260
  ```json
260
261
  {
261
- "name": "unifi_async_start",
262
+ "name": "unifi_execute",
262
263
  "arguments": {
263
- "tool": "unifi_upgrade_device",
264
- "arguments": {
265
- "mac_address": "aa:bb:cc:dd:ee:ff",
266
- "confirm": true
267
- }
264
+ "tool": "unifi_list_clients",
265
+ "arguments": {}
268
266
  }
269
267
  }
270
268
  ```
271
269
 
272
- **Response:**
273
- ```json
274
- {
275
- "jobId": "af33b233cbdc860c"
276
- }
277
- ```
270
+ **Batch Execution (parallel, async):**
271
+
272
+ For bulk operations or long-running tasks, use batch mode:
278
273
 
279
- **Check job status:**
280
274
  ```json
281
275
  {
282
- "name": "unifi_async_status",
276
+ "name": "unifi_batch",
283
277
  "arguments": {
284
- "jobId": "af33b233cbdc860c"
278
+ "operations": [
279
+ {"tool": "unifi_get_client_details", "arguments": {"mac": "aa:bb:cc:dd:ee:ff"}},
280
+ {"tool": "unifi_get_client_details", "arguments": {"mac": "11:22:33:44:55:66"}}
281
+ ]
285
282
  }
286
283
  }
287
284
  ```
288
285
 
289
- **Response (running):**
286
+ **Response:**
290
287
  ```json
291
288
  {
292
- "status": "running",
293
- "started": 1234567890.123
289
+ "jobs": [
290
+ {"index": 0, "tool": "unifi_get_client_details", "jobId": "af33b233cbdc860c"},
291
+ {"index": 1, "tool": "unifi_get_client_details", "jobId": "bf44c344dcde971d"}
292
+ ],
293
+ "message": "Started 2 operation(s). Use unifi_batch_status to check progress."
294
294
  }
295
295
  ```
296
296
 
297
- **Response (completed):**
297
+ **Check batch status:**
298
298
  ```json
299
299
  {
300
- "status": "done",
301
- "result": {...},
302
- "started": 1234567890.123,
303
- "finished": 1234567891.456
300
+ "name": "unifi_batch_status",
301
+ "arguments": {
302
+ "jobIds": ["af33b233cbdc860c", "bf44c344dcde971d"]
303
+ }
304
304
  }
305
305
  ```
306
306
 
307
- **Response (error):**
307
+ **Response:**
308
308
  ```json
309
309
  {
310
- "status": "error",
311
- "error": "Device not found",
312
- "started": 1234567890.123,
313
- "finished": 1234567891.456
310
+ "jobs": [
311
+ {"jobId": "af33b233cbdc860c", "status": "done", "result": {...}},
312
+ {"jobId": "bf44c344dcde971d", "status": "done", "result": {...}}
313
+ ]
314
314
  }
315
315
  ```
316
316
 
317
317
  **Notes:**
318
+ - Use `unifi_execute` for single operations (returns result directly)
319
+ - Use `unifi_batch` + `unifi_batch_status` for parallel/bulk operations
318
320
  - Jobs are stored in-memory only (no persistence)
319
321
  - Job IDs are unique per server session
320
- - Failed jobs capture exception details in the `error` field
321
322
 
322
323
  ### Using with Claude Desktop
323
324
 
@@ -344,22 +345,32 @@ Practical examples showing programmatic usage:
344
345
  ```python
345
346
  from mcp import ClientSession, stdio_client
346
347
 
347
- # Query tool index
348
- result = await session.call_tool("unifi_tool_index", {})
348
+ # Discover tools
349
+ tools = await session.call_tool("unifi_tool_index", {})
350
+
351
+ # Execute a single tool (returns result directly)
352
+ result = await session.call_tool("unifi_execute", {
353
+ "tool": "unifi_list_clients",
354
+ "arguments": {}
355
+ })
349
356
 
350
- # Use async jobs for long operations
351
- job = await session.call_tool("unifi_async_start", {
352
- "tool": "unifi_upgrade_device",
353
- "arguments": {"mac_address": "...", "confirm": True}
357
+ # Batch execution for parallel operations
358
+ batch = await session.call_tool("unifi_batch", {
359
+ "operations": [
360
+ {"tool": "unifi_get_client_details", "arguments": {"mac": "..."}},
361
+ {"tool": "unifi_get_device_details", "arguments": {"mac": "..."}}
362
+ ]
354
363
  })
355
364
 
356
- # Check job status
357
- status = await session.call_tool("unifi_async_status", {"jobId": job["jobId"]})
365
+ # Check batch status
366
+ status = await session.call_tool("unifi_batch_status", {
367
+ "jobIds": [j["jobId"] for j in batch["jobs"]]
368
+ })
358
369
  ```
359
370
 
360
371
  **Three complete examples:**
361
372
  - `query_tool_index.py` - Discover available tools
362
- - `use_async_jobs.py` - Background job management
373
+ - `use_async_jobs.py` - Batch operations and status checking
363
374
  - `programmatic_client.py` - Build custom Python clients
364
375
 
365
376
  See [`examples/python/README.md`](examples/python/README.md) for complete examples.
@@ -376,15 +387,18 @@ The server advertises its capabilities via an MCP identity file at [`.well-known
376
387
  "capabilities": {
377
388
  "tools": true,
378
389
  "tool_index": true,
379
- "async_operations": true
390
+ "batch_operations": true
380
391
  },
381
392
  "features": {
382
393
  "tool_index": {
383
394
  "tool": "unifi_tool_index"
384
395
  },
385
- "async_operations": {
386
- "start_tool": "unifi_async_start",
387
- "status_tool": "unifi_async_status"
396
+ "execution": {
397
+ "tool": "unifi_execute"
398
+ },
399
+ "batch_operations": {
400
+ "start_tool": "unifi_batch",
401
+ "status_tool": "unifi_batch_status"
388
402
  }
389
403
  }
390
404
  }
@@ -504,6 +518,44 @@ The server merges settings from **environment variables**, an optional `.env` fi
504
518
  | `UNIFI_VERIFY_SSL` | Set to `false` if using self-signed certs |
505
519
  | `UNIFI_CONTROLLER_TYPE` | Controller API path type: `auto` (detect), `proxy` (UniFi OS), `direct` (standalone). Default `auto` |
506
520
  | `UNIFI_MCP_HTTP_ENABLED` | Set `true` to enable optional HTTP SSE server (default `false`) |
521
+ | `UNIFI_AUTO_CONFIRM` | Set `true` to auto-confirm all mutating operations (skips preview step). Ideal for workflow automation (n8n, Make, Zapier). Default `false` |
522
+ | `UNIFI_TOOL_REGISTRATION_MODE` | Tool loading mode: `lazy` (default), `eager`, or `meta_only`. See [Context Optimization](#context-optimization) |
523
+ | `UNIFI_ENABLED_CATEGORIES` | Comma-separated list of tool categories to load (eager mode). See table below |
524
+ | `UNIFI_ENABLED_TOOLS` | Comma-separated list of specific tool names to register (eager mode) |
525
+
526
+ ### Tool Categories (for UNIFI_ENABLED_CATEGORIES)
527
+
528
+ When using eager mode with category filtering, these are the valid category names:
529
+
530
+ | Category | Description | Example Tools |
531
+ |----------|-------------|---------------|
532
+ | `clients` | Client listing, blocking, guest auth | `unifi_list_clients`, `unifi_block_client` |
533
+ | `config` | Configuration management | - |
534
+ | `devices` | Device listing, reboot, locate, upgrade | `unifi_list_devices`, `unifi_reboot_device` |
535
+ | `events` | Events and alarms | `unifi_list_events`, `unifi_list_alarms` |
536
+ | `firewall` | Firewall rules and groups | `unifi_list_firewall_rules`, `unifi_create_firewall_rule` |
537
+ | `hotspot` | Vouchers for guest network | `unifi_list_vouchers`, `unifi_create_voucher` |
538
+ | `network` | Network/VLAN management | `unifi_list_networks`, `unifi_create_network` |
539
+ | `port_forwards` | Port forwarding rules | `unifi_list_port_forwards` |
540
+ | `qos` | QoS/traffic shaping rules | `unifi_list_qos_rules`, `unifi_create_qos_rule` |
541
+ | `routing` | Static routes (V1 API) | `unifi_list_routes`, `unifi_create_route` |
542
+ | `stats` | Statistics and metrics | `unifi_get_client_stats`, `unifi_get_device_stats` |
543
+ | `system` | System info, health, settings | `unifi_get_system_info`, `unifi_get_network_health` |
544
+ | `traffic_routes` | Policy-based routing (V2 API) | `unifi_list_traffic_routes` |
545
+ | `usergroups` | Bandwidth profiles/user groups | `unifi_list_usergroups`, `unifi_create_usergroup` |
546
+ | `vpn` | VPN servers and clients | `unifi_list_vpn_servers`, `unifi_list_vpn_clients` |
547
+
548
+ **Example usage:**
549
+ ```bash
550
+ # Load only client and system tools
551
+ export UNIFI_TOOL_REGISTRATION_MODE=eager
552
+ export UNIFI_ENABLED_CATEGORIES=clients,system
553
+
554
+ # Or load specific tools only
555
+ export UNIFI_ENABLED_TOOLS=unifi_list_clients,unifi_list_devices,unifi_get_system_info
556
+ ```
557
+
558
+ **Note:** Tools may also be filtered by the `permissions` section in config.yaml (e.g., `clients.update: false` blocks mutating client tools).
507
559
 
508
560
  ### Controller Type Detection
509
561
 
@@ -826,6 +878,37 @@ See [docs/permissions.md](docs/permissions.md) for complete documentation includ
826
878
  * `unifi_force_reconnect_client`
827
879
  * `unifi_authorize_guest`
828
880
  * `unifi_unauthorize_guest`
881
+ * `unifi_set_client_ip_settings`
882
+
883
+ ### Events & Alarms
884
+
885
+ * `unifi_list_events`
886
+ * `unifi_list_alarms`
887
+ * `unifi_archive_alarm`
888
+ * `unifi_archive_all_alarms`
889
+ * `unifi_get_event_types`
890
+
891
+ ### Routing (Static Routes)
892
+
893
+ * `unifi_list_routes`
894
+ * `unifi_get_route_details`
895
+ * `unifi_create_route`
896
+ * `unifi_update_route`
897
+ * `unifi_list_active_routes`
898
+
899
+ ### Hotspot (Vouchers)
900
+
901
+ * `unifi_list_vouchers`
902
+ * `unifi_get_voucher_details`
903
+ * `unifi_create_voucher`
904
+ * `unifi_revoke_voucher`
905
+
906
+ ### User Groups
907
+
908
+ * `unifi_list_usergroups`
909
+ * `unifi_get_usergroup_details`
910
+ * `unifi_create_usergroup`
911
+ * `unifi_update_usergroup`
829
912
 
830
913
  ### Statistics & Alerts
831
914
 
@@ -1002,9 +1085,9 @@ uv run python devtools/dev_console.py
1002
1085
 
1003
1086
  # You'll see a menu of all tools including:
1004
1087
  # - unifi_tool_index (list all tools with schemas)
1005
- # - unifi_async_start (start background jobs)
1006
- # - unifi_async_status (check job status)
1007
- # - All 60+ UniFi tools (clients, devices, networks, etc.)
1088
+ # - unifi_execute (run any discovered tool)
1089
+ # - unifi_batch / unifi_batch_status (parallel operations)
1090
+ # - All 80+ UniFi tools (clients, devices, networks, etc.)
1008
1091
  ```
1009
1092
 
1010
1093
  **4. Test with Python examples:**
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unifi-network-mcp"
3
- version = "0.2.0"
3
+ version = "0.3.1"
4
4
  description = "Unifi Network MCP Server"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -58,3 +58,15 @@ dev-dependencies = [
58
58
  [project.scripts]
59
59
  # CLI entrypoint that launches the UniFi Network MCP server
60
60
  unifi-network-mcp = "src.main:main"
61
+
62
+ [tool.ruff]
63
+ line-length = 120
64
+
65
+ [tool.ruff.lint]
66
+ select = ["E", "F", "I"]
67
+ ignore = [
68
+ "E501", # Line too long - handled by line-length setting above
69
+ ]
70
+
71
+ [tool.ruff.lint.isort]
72
+ known-first-party = ["src"]
@@ -12,13 +12,13 @@ Importing it early guarantees deterministic side‑effects (env + logging) and
12
12
  exposes a `load_config()` helper that the rest of the codebase can share.
13
13
  """
14
14
 
15
- from dataclasses import dataclass
15
+ import importlib.resources
16
16
  import logging
17
17
  import os
18
18
  import sys
19
+ from dataclasses import dataclass
19
20
  from pathlib import Path
20
21
  from typing import Any
21
- import importlib.resources
22
22
 
23
23
  from dotenv import load_dotenv
24
24
  from omegaconf import OmegaConf
@@ -118,32 +118,20 @@ def load_config(path_override: str | Path | None = None) -> OmegaConf:
118
118
  relative_path = Path("config/config.yaml")
119
119
  if relative_path.exists() and relative_path.is_file():
120
120
  resolved_path = relative_path
121
- logger.info(
122
- "Using configuration file from relative path: %s", relative_path
123
- )
121
+ logger.info("Using configuration file from relative path: %s", relative_path)
124
122
  else:
125
123
  # 3. Use bundled default config
126
124
  try:
127
125
  # Use importlib.resources to safely access package data
128
- config_file_ref = importlib.resources.files("src.config").joinpath(
129
- "config.yaml"
130
- )
126
+ config_file_ref = importlib.resources.files("src.config").joinpath("config.yaml")
131
127
  if config_file_ref.is_file():
132
- resolved_path = Path(
133
- str(config_file_ref)
134
- ) # Convert Traversable to Path
135
- logger.info(
136
- "Using bundled default configuration: %s", resolved_path
137
- )
128
+ resolved_path = Path(str(config_file_ref)) # Convert Traversable to Path
129
+ logger.info("Using bundled default configuration: %s", resolved_path)
138
130
  else:
139
- logger.error(
140
- "Bundled default configuration file could not be accessed (not a file)."
141
- )
131
+ logger.error("Bundled default configuration file could not be accessed (not a file).")
142
132
  raise SystemExit(3) # Exit if bundled config isn't a file
143
133
  except (ModuleNotFoundError, FileNotFoundError, Exception) as e:
144
- logger.error(
145
- "Could not find or access bundled default configuration: %s", e
146
- )
134
+ logger.error("Could not find or access bundled default configuration: %s", e)
147
135
  raise SystemExit(3) # Exit if bundled config cannot be loaded
148
136
 
149
137
  if resolved_path is None: