Releases: swkstudios/unifi-fabric-mcp-server
Release list
v0.6.2
Added
- Optional
filterargument on five Network Integration collection tools (list_networks,list_clients,list_site_devices,list_firewall_policies,list_wifi_broadcasts); expression forwarded unchanged to the upstream UniFi filter parameter; applied in both drain and single-page modes. (#187) - Two port-profile VLAN-tagging tools:
allow_network_on_port_profile(removes a network from a profile's exclusion list, atomically fixing the D12 auto-exclusion footgun) andexclude_network_on_port_profile(inverse). Both requireconfirm=True. (#197) - Eight Mobility tools (workspace-scoped --
workspace_id/device_id, not host/site) for UMR mobile routers: 5 reads (list_mobility_workspaces,list_mobility_admins,list_mobility_devices,get_mobility_device,list_mobility_clients) and 3 guarded writes (update_mobility_device_name,update_mobility_device_network,update_mobility_device_wireless). Writes requireconfirm=true+UNIFI_ENABLE_MOBILITY_WRITE(default off, #186). (#196) - Eleven Carrier / ISP Fabric subscriber tools (org-scoped, no host/site): 4 reads (
list_carrier_subscribers,get_carrier_subscriber,list_carrier_service_plans,get_carrier_service_plan) and 7 guarded writes (create_carrier_subscriber,update_carrier_subscriber,attach_carrier_subscriber_host,detach_carrier_subscriber_host,assign_carrier_subscriber_plan,suspend_carrier_subscriber,resume_carrier_subscriber). Writes requireconfirm=true+UNIFI_ENABLE_CARRIER_FABRIC_WRITE(default off). Not testable against the maintainer's live hardware; hermetic/spec-conformance tested only. (#201, #207) - Five Fabric connector relay tools (
fabric_connector_get/post/put/patch/delete) -- a guarded escape hatch to reach console routes without a typed wrapper. GET always available; mutations requireconfirm=true+UNIFI_ENABLE_CONNECTOR_WRITE(default off). Guards: Registry-only identity, namespace allowlist, positive-charset path validation, scope guard, credential-field redaction, per-mutation audit log. (#189) - 41 Protect Integration API tools via the published v7.1.87 API: arm profiles (8), sirens (6), fobs (3), relays (4), speakers (4), bridges (3), link stations (3), alarm hubs (4), Protect users (2), ULP users (2), application metadata (1), POS ingestion (1). Write/action tools gated by
UNIFI_PROTECT_MUTATIONS_ENABLED(default on); physical/irreversible actions additionally requireconfirm=true. (#185) - Three read-only stat convenience tools (no new controller routes):
get_client_link_diagnostics(per-client link/QoS record from/stat/sta),get_device_port_state(port table + LLDP + thermal/power from/stat/device),get_device_stp_state(per-device and per-port STP/RSTP fields). (#183, #184, #188) - Five InnerSpace read tools completing the GET-only Integration API v1.3.23 coverage:
list_innerspace_floor_plans,list_innerspace_access_points,list_innerspace_switches,list_innerspace_inventory,get_innerspace_asset(floor-plan image, base64 inline under 10 MiB cap). - Catalog regression check (
unifi_fabric._catalog_audit,scripts/check_catalog_regression.py) comparing registered tools against what the MCP gateway serves -- catches phantom/stale drift the static checks cannot see. - Testing-procedure documentation: app-family testability matrix (noting which families cannot be live-tested on the maintainer's hardware) and namespace testing (
/v1vs./ea) sections. - Testing procedure: added explicit Tier 2 rule prohibiting use of the MCP session tool list as gateway catalog evidence (session-start snapshot goes stale when a deployment lands mid-session); documented all three authoritative catalog sources with exact invocations (the deployed gateway's catalog database, the catalog-refresh job log, check_catalog_regression.py --live); added required verification protocol before filing a missing-tool finding; added new bullet to "What a reviewer must never do" for session-catalog escalation; added new "General Evidence Integrity Principle" section with three concrete dated examples of stale artifacts being mistaken for live state (2026-08-15). (#222)
- Spec-drift watcher (
unifi_fabric._spec_drift,scripts/check_spec_drift.py) that tracks the published UniFi OpenAPI contracts per app family. It parses thedeveloper.ui.com/llms.txtroot index (so a new family publishing an API for the first time is detected, not just version bumps within known ones), fetches each service's OpenAPI spec, and diffs the signed path+method set against a committed baseline snapshot -- reporting NEW / CHANGED / REMOVED endpoints, VERSION_DRIFT, NEW/REMOVED services, and (informational) published-vs-console version gaps. A per-service coverage overlay flags a CHANGED/REMOVED endpoint we implement as breakage and a NEW write route on a covered family (e.g. the day InnerSpace grows one) as an unimplemented capability. Runs hermetically (--snapshot, CI-safe) or live (--live, public specs, no credentials), mirroring thecheck_catalog_regression.pyprecedent (#190 / PR #195). (#199) - URL-pinning tests for all five Site Manager families asserting the exact
/v1/path each tool constructs (intest_site_manager.py,test_registry.py,test_aggregation.py), so a future namespace regression fails at Tier 1 rather than in a live run. (#198) - Testing procedure: documented Tier-2 host-scoped coverage requirements (required env vars per family, ephemeral run recipe); documented intentional exclusion of live VLAN CRUD under the standing no-live-mutation constraint, distinguishing it from STP reads and per-console Network reads that must run; added source-pinning section requiring all test claims to cite code at the build-under-test revision. (#228)
Changed
allow_network_on_port_profileandexclude_network_on_port_profilenow requireconfirm=True-- each writes to a shared port profile affecting every assigned port. (#197)- Port-profile tools (
list_port_profiles,get_port_profile,update_port_profile) resolve networkconf id fields (excluded_networkconf_ids,native_networkconf_id,voice_networkconf_id) to{id, name, vlan}objects instead of bare 24-hex ids. Write path unchanged. (#197) create_networkresponses carry awarnings.D12_AUTO_EXCLUSIONentry listing every custom-tagged port profile that UniFi silently auto-excluded the new network from. (#197)- InnerSpace read tools now try the documented Integration API path first, falling back to the legacy
/apipath on a 403; error message now names both plausible causes and both attempted paths. create_firewall_policy/update_firewall_policydocstrings document thetrafficFilterschema and the port-filter placement footgun (source vs. destination port targeting). (swkstudios#17)update_port_profileandexecute_port_actiondocstrings gained explicit scope guards (shared-profile and operational-action-only warnings respectively).get_innerspace_assetdocstring clarifies thatplan_idis the asset-group id fromimage_url, not the floor plan's ownid.- Versioning scheme: dev-branch pushes now cut semver pre-release tags
(vX.Y.Z-dev.N) against the in-flight release target declared inpyproject.toml,
instead of bumping the patch and cutting a full release tag on every push. The clean
release tag (vX.Y.Z, no suffix) is cut only as a deliberate release step. Docker
stable aliases (:latest,:X.Y) move only for clean releases; pre-release builds
publish an immutable:X.Y.Z-dev.Nimage plus the:devbranch tag. The public
release publish rejects pre-release tags outright. - Migrated the five Site Manager endpoint families (
hosts,sites,devices,isp-metrics,sd-wan-configs) from the Early Access/ea/namespace to the stable Official/v1/namespace in place — same tool names, no behavioral change. Call sites updated intools/site_manager.py,registry.py, andtools/aggregation.py. Live-verified against a test console:/ea/*and/v1/*return identical response envelopes, and both/ea/sitesand/v1/sitesreturn the same Fabric ObjectIdsiteId(disproving the earlier assumption that/v1/sitesreturns a UUID — the UUID used in proxy URLs comes only from the per-console connector/sitesendpoint and is unaffected). Codifies the standing policy: new Site Manager tools target/v1/;/ea/is permitted only where no/v1/equivalent is served and must carry an "Early Access — endpoint may change" note in its docstring. (#198, #192) list_sites/ server tool guidance no longer describessiteIdas an "EA-internal" ID; it is a Site Manager Fabric ObjectId served on/v1/sites. (#192)- Reworded the
exclude_network_on_port_profiledocstring (and the generateddocs/TOOLS.md) to describe it as the inverse ofallow_network_on_port_profile; the pairedtools/network_services_proxy.pyimplementation docstring was updated to match. Documentation-only cross-reference wording; no behavioral change. - Replaced real captured InnerSpace asset-group and plan GUIDs in the
get_innerspace_assetdocstring example and test fixtures with synthetic RFC 4122 values; reworded the example so it is not presented as an observed live value; replaced a truncated fragment of the same real GUID in filename examples. Pre-publication hygiene; no behavior change. (#256) - Replaced a QA label and possessive phrasing in tool docstrings, the AI-facing INSTRUCTIONS block, and a runtime gated-write message with plain language describing the testability limitation ("not testable against the maintainer's live hardware; hermetic/spec-conformance tested only") -- wording already used in the README.
docs/TOOLS.mdregenerated accordingly. The technical caveat (spec/hermetic-conformance tested, never exercised against real hardware) is preserved; n...
v0.6.0
Added
list_protect_eventsgained server-side event filters, each verified against the
live API to actually narrow the returned set (not silently accepted and ignored):smart_detect_types— filter smart-detect events by subtype:person,
vehicle,animal,package,face,licensePlate, and the audio-alarm
subtypesalrmSpeak,alrmSiren,alrmBark,alrmCarHorn. This is a distinct
parameter fromtypes; the API only applies it whentypesis also set to the
relevant event type(s), so passing it alone — which the API silently ignores —
is rejected with a clear, actionable error instead of returning everything.categories— filter by event category (for examplemotion,smart,iot,
admin). Values the API does not recognise are ignored by the API upstream.without_descriptions— opt-in flag to ask the API to omit each event's
descriptionblock for a smaller payload. Off by default; full-fidelity records
remain the default and descriptions are never dropped automatically.camerasnow accepts camera names as well as IDs, resolved case-insensitively
(matching the name-or-ID convention already used forhost). An unknown name
raises a clear error listing the available cameras instead of silently matching
nothing.
- Face and vehicle recognition tools for UniFi Protect consoles, surfacing Protect's
built-in subject recognition through five read-only tools:list_recognition_groups— list recognised subjects (for example enrolled faces),
each with its label, sighting count, and first/last-seen timestamps. Supports
filtering to named subjects only and server-side sorting; the complete set is
returned by default rather than just the first page.get_recognition_group_counts— aggregate totals (how many subjects exist, how
many are named, and so on).get_recognition_group_image— the reference crop for a subject, returned as a
base64-encoded JPEG.list_recognition_detections— every sighting of a subject, each with its match
confidence and a thumbnail reference. Accepts an optional time window (verified
against the live API to filter server-side), so a caller can ask for a specific
range — the last hour, 30 days, or 90 days — directly; the complete set for the
window is returned by default.get_thumbnail— the crop for an individual sighting, returned as a base64-encoded
JPEG.
Registry.resolve_key_for_host()— resolves which configured API key owns a
given host (by id, hostname, or name) for MSP multi-key deployments. Queries
each key's cached host list concurrently with per-key failure isolation;
raises only if every key fails. Single-key deployments short-circuit with no
extra API calls. This is the foundation for threading the owning key through
the per-host tools (follow-up).- InnerSpace floor-plan tools (read-only) over the console connector proxy:
get_innerspace_summary— structural inventory (shape counts by type,
per-floor plans and scales, product/material dictionary sizes) without the
full payload.get_innerspace_project— full project geometry, with amodeparameter
(2D/3D, default3D; only 3D carries real metric device heights).list_innerspace_devices— placed device shapes with position and rotation.- Responses are returned verbatim, including device
meta.mac/meta.ipand
floor-plan image/asset URLs (the server is a faithful pass-through). A
truncated host id (which the API rejects with403 forbidden: host not found) now returns an explanation pointing at the composite host id rather
than implying InnerSpace is unavailable.
- History and session tools (read-only) surfacing data beyond the live snapshot:
list_client_sessions— per-site session history via the classic REST
/stat/sessionendpoint (~90-day retention, epoch-seconds timestamps).get_historical_stats— bucketed traffic reports from/stat/report
(5-minute, hourly, or daily buckets; timestamps are epoch-milliseconds at
the wire level, but callers always pass epoch-seconds — conversion is
handled internally).list_known_clients— full per-site client roster including offline
devices, from/stat/alluser(GET).list_protect_events— historical Protect events via the private REST
proxy path (the Integration API exposes events over WebSocket only; this
tool uses the REST fallback). Sensor events promotemetadata.sensorId.text
to the top level for easier filtering.- All responses are returned verbatim, including MAC addresses, IPs,
hostnames, and client names (the server is a faithful pass-through).
- Schema<->instructions cross-check: a test now parses every tool name referenced
in the serverinstructionsblock and asserts each one is actually registered,
so the agent-facing documentation can no longer advertise a tool that does not
exist. Runs on every change with no live server needed; a live variant against a
running server is also available for pre-publication checks. - Parameter-scope cross-check: a second static test now parses the "Parameter Scope
Quick Reference" buckets in the serverinstructionsblock and asserts each
explicitly-named tool's declared host/site scope matches its registered schema, so a
tool filed under the wrong bucket (e.g. "ID only" for a tool that actually requires
host/site) fails CI. The phantom-name check's blind spot — it verifies tool
names, not the claims around them — is now documented in the audit module and the
testing procedure.
Changed
-
Breaking (tool parameters):
get_radius_profile,update_vpn_server,
delete_vpn_server,update_hotspot_operator, anddelete_hotspot_operator
now requirehostandsitearguments. These tools previously took only an
item ID because they targeted a (non-functional) global/ea/path. The
working routes are per-console and per-site, so the console/site must now be
named.get_vpn_serveralready requiredhost/siteand is unchanged.
Callers of the affected tools must addhostandsite. -
Response shape: recognition list tools stay resource-named, not
data.
list_recognition_groupsreturns its array undergroups(and
list_recognition_detectionsunderdetections), matching the codebase
convention of naming a list's array after its resource. This is deliberately
left unchanged; only the Network Integration offset-paginated proxy lists use
the{data, totalCount}shape. Thelist_recognition_groupsdocstring now
states this explicitly so consumers key offgroupsrather than assumingdata. -
All tools now return complete upstream data, including identifier and
credential fields. The server is a faithful pass-through: whatever the
UniFi API returns for a request is returned to the caller unchanged. Runtime
response filtering has been removed everywhere it existed:- Client/session/roster and Protect event responses now include MAC
addresses, IPs, hostnames, and client/sensor/subject names verbatim
(previously replaced with[REDACTED]). - Controller settings, Dynamic DNS, WLAN configs, and RADIUS account
responses now include credential fields such asx_passphrase,
x_password, and API tokens verbatim (previously replaced with
[REDACTED]). - InnerSpace project geometry now includes device
meta.mac/meta.ipand
floor-plan image/asset URLs verbatim (previously replaced with
[REDACTED], which broke floor-plan image retrieval). - Site Manager host records now include
reportedStateGPS coordinates
(latitude,longitude,geoInfo) verbatim.
User-visible behavior change: responses are larger and contain data
earlier versions withheld. A deployment that needs to restrict what reaches a
consumer should layer that policy on top of the server (a proxy or wrapper),
which can always narrow a faithful response — whereas data the server chose
to withhold could never be recovered downstream. - Client/session/roster and Protect event responses now include MAC
-
Removed the
include_secretsparameter fromlist_dynamic_dns,
get_dynamic_dns,list_wlan_configs,get_wlan_config,list_accounts,
andget_account, and theinclude_gpsparameter fromlist_hostsand
get_host. These opt-in flags gated the now-removed filtering and are
redundant — the fields they exposed are always returned. Calls that passed
either parameter should drop it. (Removing the_token-suffix credential
match also fixes a latent bug where the pagination cursornextTokenwas
being replaced with[REDACTED].) -
List tools drain all pages by default and return the complete result set.
When called without explicit pagination parameters, list tools aggregate all
available pages before returning. Typical queries — "list all sites", "list
all devices" — complete without requiring callers to follow next-page tokens
manually.To retrieve a single page instead, supply explicit pagination parameters:
for cursor-based tools, passpage_token=<token>from a prior response; for
offset-based tools, pass bothoffsetandlimit.When
UNIFI_PAGINATE_MAX_PAGESis set and the page cap is reached before
results are exhausted, the response includes"incomplete": trueand an
"incompleteReason"string. Callers should surface this to users so they
know results may be partial.User-visible behavior change: tools that silently stopped at one page now
return every matching record. Affected endpoints, each confirmed paginated
against the live API:list_protect_events— historical Protect events are offset-paginated; a
wide time window can hold tens of thousands of events, and an unpaginated
request times out on the device. The tool now pages through them (200 at a
time) and returns the complete, time-ordered set. Pass `offs...
v0.3.155
v0.3.154
Fixed
- Pagination: preserve explicit zero params (#87)
- Validate network service path IDs
Changed
fastmcpdependency now>=3.2,<3.4- Bump
actions/create-github-app-tokento v3
Docs
- Clarify dispatch-based public publish flow (#85)
- Regenerate TOOLS.md from runtime metadata
v0.3.153 — UniFi Fabric MCP Server
What's New
Features
- Controllable server logging — new
UNIFI_LOG_LEVELenv var to set log verbosity (DEBUG,INFO,WARNING, etc.) - Auto-pull workflow for approved public issues — approved issues filed on the public repo are automatically tracked upstream
Bug Fixes
- Prevent Authorization header leak in exception messages — credentials are now redacted from error output
- Idempotency check improvement — issue-pull deduplication now matches both full URLs and shorthand formats
Documentation
- Added Troubleshooting section to README
- Auto-generated TOOLS.md reference manual from
@mcp.tool()exports - Documented retry/backoff behavior in docstrings and README
- Added finding verification protocol to testing procedure
CI/CD
- Mirror tag management improvements (orphan tag pruning, delete-tag workflow)
Full changelog: https://github.com/swkstudios/unifi-fabric-mcp-server/blob/main/CHANGELOG.md
v0.3.14 — UniFi Fabric MCP Server
Added
- Optional
MCP_BEARER_TOKENenv var for bearer-token authentication on HTTP transport - When set, all incoming MCP requests require
Authorization: Bearer <token>header - Backward compatible: server runs without transport auth when unset (existing behavior)
Full changelog: https://github.com/swkstudios/unifi-fabric-mcp-server/blob/main/CHANGELOG.md