fix: Network/VLAN information not displayed for clients#60
Conversation
All network clients show as 'Unknown (VLAN N/A)' due to field name mismatch between exporter output and MCP wrapper code. The exporter provides network info in a 'network' field (network name), but the wrapper expects 'network_id' (UUID). Changes: - format_network_clients(): Use network name lookup instead of UUID - format_network_summary(): Use network name lookup instead of UUID - get_client_details(): Use network name lookup instead of UUID Fixes VLAN segmentation visibility where all clients appeared under 'Unknown' network regardless of actual VLAN assignment.
There was a problem hiding this comment.
Pull request overview
Fixes incorrect “Unknown (VLAN N/A)” client network/VLAN display in the UniFi MCP server by aligning client-to-network lookups with the exporter’s client network fields.
Changes:
- Update
format_network_clients()to group clients by exported network name and resolve VLAN via a name-keyed network lookup. - Update
format_network_summary()to compute “Top networks” using network names and show VLAN from matching network config. - Update
get_client_details()to display and resolve a client’s network/VLAN using the exported network name.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| clients = data.get("clients", []) | ||
| networks = {n["_id"]: n for n in data.get("networks", [])} | ||
| # Create network lookup by name (exporter uses "network" field with name, not ID) | ||
| networks = {n["name"]: n for n in data.get("networks", [])} |
There was a problem hiding this comment.
networks = {n["name"]: n ...} will throw KeyError if any network object is missing name. Build this lookup defensively (use n.get("name") and skip missing names) so get_client_details can’t fail due to one malformed network entry.
| networks = {n["name"]: n for n in data.get("networks", [])} | |
| networks = {} | |
| for n in data.get("networks", []): | |
| name = n.get("name") | |
| if not name: | |
| continue | |
| networks[name] = n |
| clients = data.get("clients", []) | ||
| networks = {n["_id"]: n for n in data.get("networks", [])} | ||
| # Create network lookup by name (exporter uses "network" field with name, not ID) | ||
| networks_by_name = {n["name"]: n for n in data.get("networks", [])} |
There was a problem hiding this comment.
networks_by_name = {n["name"]: n ...} will raise a KeyError if any network entry lacks a name field (elsewhere in the repo network names are accessed via net.get("name", "Unknown"), suggesting this can be missing). Build this mapping defensively (e.g., use n.get("name") and skip falsy names) so listing clients can’t crash on unexpected controller responses.
| networks_by_name = {n["name"]: n for n in data.get("networks", [])} | |
| networks_by_name = { | |
| n_name: n | |
| for n in data.get("networks", []) | |
| if (n_name := n.get("name")) | |
| } |
| # Exporter provides "network" field with network name, not "network_id" | ||
| network_name = client.get("network", "Unknown") |
There was a problem hiding this comment.
This groups clients using client.get("network") as a network name, but unifi_exporter.py exports both network and network_name and even uses network_name for grouping in its own output. To avoid reintroducing the display bug on controllers where network is an ID/code, prefer network_name for display/grouping and fall back to network only when network_name is missing.
| # Exporter provides "network" field with network name, not "network_id" | |
| network_name = client.get("network", "Unknown") | |
| # Prefer human-readable network_name; fall back to network (ID/code) and then "Unknown" | |
| network_name = client.get("network_name") or client.get("network") or "Unknown" |
| output += f"\nTOP NETWORKS:\n" | ||
| networks_dict = {n["_id"]: n for n in networks} | ||
| for network_id, count in sorted( | ||
| networks_dict = {n["name"]: n for n in networks} |
There was a problem hiding this comment.
networks_dict = {n["name"]: n for n in networks} can raise KeyError if any network object lacks name. Consider using n.get("name") and skipping entries without a name so get_network_stats can still render even with partial/odd controller data.
| networks_dict = {n["name"]: n for n in networks} | |
| networks_dict = {n.get("name"): n for n in networks if n.get("name")} |
| # Exporter provides "network" field with network name, not "network_id" | ||
| network_name = client.get("network", "Unknown") |
There was a problem hiding this comment.
network_name = client.get("network") assumes the network field is the human-readable name. Since the exporter also provides network_name, and uses it elsewhere for grouping, prefer client.get("network_name") for the displayed name (with a fallback to network) so client details don’t show raw IDs/codes on some controller versions.
| # Exporter provides "network" field with network name, not "network_id" | |
| network_name = client.get("network", "Unknown") | |
| # Prefer human-readable network_name, fall back to network if needed | |
| network_name = client.get("network_name") or client.get("network", "Unknown") |
Address Copilot review feedback:
- Use n.get("name") with walrus operator to avoid KeyError on networks
without a name field in format_network_clients() and get_client_details()
- Use n.get("name") filter in format_network_summary() networks_dict
- Prefer network_name field over network for display (more reliable
human-readable name); fall back to network then Unknown
|
@claude can you review and let me know if this is safe to merge, or if there are additional updates needed. |
|
I'll analyze this and get back to you. |

Pull Request: Fix Network/VLAN Information Not Displayed for Clients
Summary
All network clients show as "Unknown (VLAN N/A)" even though network and VLAN data exists. This is caused by a field name mismatch between the exporter output format and the MCP wrapper code expectations.
Issue Description
Current Behavior
Expected Behavior
Root Cause
File:
unifi_mcp_optimized.pyField Name Mismatch
The
unifi_exporter.pyprovides client network information in a"network"field (containing the network name as a string), but the MCP wrapper code expects a"network_id"field (containing a UUID).Exporter Output Format
{ "clients": [ { "hostname": "smart-device", "ip": "192.168.30.100", "mac": "aa:bb:cc:dd:ee:ff", "network": "IoT", ← Provides network NAME (string) "network_name": "Unknown", "is_wired": false } ], "networks": [ { "_id": "5eaba21f8b76ce04eb94a66d", ← Network UUID "name": "IoT", ← Network NAME "vlan": 30 } ] }Current MCP Code Problem
The code creates a networks lookup dictionary keyed by UUID (
_id), then tries to find client networks usingnetwork_id(which doesn't exist in client objects):Proposed Fix
Change the lookup strategy to use network names instead of UUIDs, matching the exporter's output format.
File:
unifi_mcp_optimized.pyThree functions need updates:
1. Function:
format_network_clients()(Lines 338-376)Current Code:
Fixed Code:
2. Function:
format_network_summary()(Lines 413-427)Current Code:
Fixed Code:
3. Function:
get_client_details()(Lines 632-650)Current Code:
Fixed Code:
Impact
Before Fix
After Fix
Testing
Test Environment
Test Case 1: List Clients by Network
Command:
list_clientsExpected Result:
Test Case 2: Network Summary
Command:
get_network_statsExpected Result:
Test Case 3: Individual Client Details
Command:
get_client_details("device-name")Expected Result:
Verification Steps
rm -f /tmp/unifi_mcp_cache/unifi_data.jsonlist_clientsand verify networks show correctlyget_network_statsand verify TOP NETWORKS sectionget_client_detailsfor devices on different VLANsFiles Changed
unifi_mcp_optimized.pyformat_network_clients()to use network name lookupformat_network_summary()to use network name lookupget_client_details()to use network name lookupBackward Compatibility
✅ Fully backward compatible
Why This Bug Exists
The field name mismatch suggests one of the following:
unifi_exporter.pyoutput format changed butunifi_mcp_optimized.pywasn't updatedThe exporter clearly provides network names in the
"network"field, but the wrapper was coded to expect UUIDs in a"network_id"field that doesn't exist.Related Issues
None found. This appears to be the first report of this issue, possibly because:
Checklist