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. Passoffset/limitto
fetch a single page instead.list_networks,list_firewall_zones,list_dns_policies,
list_vpn_servers,list_radius_profiles, andlist_hotspot_vouchers—
these per-site list endpoints are offset-paginated with a small native
default page size (25–100 records); larger sites were silently truncated.
They now drain all pages by default and return{data, totalCount}, and
acceptoffset/limitfor manual single-page access. A drain that hits the
safety page cap is flaggedincompleterather than truncating silently.- Internal site-name resolution now drains all pages of the per-console sites
list, so consoles with many local sites resolve site names past the first
page instead of failing.
-
list_cameraswas investigated for the same truncation risk and confirmed
not paginated against the live API (it returns a complete array with no
pagination envelope or cursor, and offset/limit have no effect); this is now
documented so it is not re-investigated. -
Protect's
allCamerasflag was investigated for potential tool exposure and
confirmed a live no-op: passingallCameras=trueandallCameras=falseto
the underlying API endpoint return identical results. The flag is not exposed
as a tool parameter.
Removed
- Internal dead code in
tools/hotspot.py: the unregistered_list_vouchers,
_create_vouchers, and_delete_voucherhelpers targeting/ea/vouchers(a
path the console does not serve) were never wired as MCP tools. The live hotspot
voucher tools (list_hotspot_vouchers,create_hotspot_vouchers,
bulk_delete_hotspot_vouchers) go through the network-services proxy and are
unaffected. No user-visible tool changes.
Fixed
- The
instructions"Parameter Scope Quick Reference" no longer listsupdate_vpn_server,
delete_vpn_server,update_hotspot_operator, anddelete_hotspot_operatorunder an
"ID only (no host/site)" bucket. All four requirehostandsite, and are already
covered under "host + site required"; the stale bullet contradicted the general rule and
the tools' own schemas, leaving an agent no recovery path. - Recognition list tools now state their response shape in the agent-facing tool
descriptions:list_recognition_groupsreturns its array undergroupsand
list_recognition_detectionsunderdetections(notdata, unlike the offset-proxy
lists). Previously only the module docstring said this, which agents never see. create_rtsps_stream/delete_rtsps_streamnow enumerate the acceptedqualities
channel names —high,medium,low,package(verified live;packageonly on
package-camera doorbells). The prior example listed a non-existenthighestchannel and
omittedpackage.execute_port_actionnow documents itsactionpayload ({'action': 'power-cycle'}to
PoE power-cycle a port) instead of an unexplained "port action payload", and notes the
endpoint is write-only so its accepted set cannot be enumerated by inspection.list_all_devicesnow warns thatstatus_filteris matched locally, so an unrecognised
value returns an empty list — a typo reads as a healthy, problem-free fleet rather than an
error. Mirrors the existingsession_typesilent-value warning.- Recognition tool docstrings now explicitly state that the
typeparameter requires
singular values (face,vehicle). Plural forms (faces,vehicles) return HTTP 400
from the upstream Protect API. Two separate agents guessed plural and misread the 400 as
"recognition not enabled." The fix applies to all four tools that accepttype:
list_recognition_groups,get_recognition_group_counts,get_recognition_group_image,
andlist_recognition_detections. - VPN server, RADIUS profile, and hotspot operator tools no longer target an
unserved API path. A family of tools was split across two API bases: the
list_*siblings reached data over the working per-console proxy (VPN/RADIUS)
or Classic REST (hotspot operators), while the correspondingget_/update_/
delete_/create_tools pointed at Site Manager/ea/paths
(/ea/vpn-servers,/ea/radius-profiles,/ea/hotspot-operators) that are
not served on the console and answer404 page not foundat the route level.
Every such tool now uses the same base its workinglist_*sibling uses,
verified live with real IDs obtained from the list tools:get_vpn_serverandget_radius_profilenow drain the per-console proxy
list (/sites/{site}/vpn/servers,/sites/{site}/radius/profiles) and filter
by ID — the Network Integration API exposes these resources as collections
only, with no item-level GET route — instead of filtering the unserved
/ea/list (which made every call fail with a 404).update_vpn_serveranddelete_vpn_servernow issuePUT/DELETEagainst
the per-console proxy item path (/sites/{site}/vpn/servers/{id}), matching
create_vpn_serverand the site-to-site tunnel tools, instead of the
unserved/ea/vpn-servers/{id}.create_hotspot_operator,update_hotspot_operator, and
delete_hotspot_operatornow use the console's Classic REST controller
(/rest/hotspotop,/rest/hotspotop/{id}), the same baselist_hotspot_operators
reads from, instead of the unserved/ea/hotspot-operators. The create body
now uses the controller'sx_passwordfield and no longer repeats host/site
IDs in the body (they are addressed by the site slug in the URL).
list_protect_eventsdocumentation listed smart-detect categories (person,
face,animal) under thetypesparameter. Upstream these belong to a
different query parameter (now exposed assmart_detect_types); passed as
typesthey match no event type and quietly return an empty set. The docstring
now documents the two parameters separately, with the accepted event types
(motion,smartDetectZone,smartAudioDetect,sensorOpened,sensorClosed,
access) verified against the live API.query_isp_metricsnow applies the requested time range. The tool previously
sentstart_time/end_timeas top-level body fields, which the UniFi Site
Manager API silently ignores — so a call asking for a specific window returned
the API's default range instead, with no error. User-visible behavior change:
results from earlier versions may not have honored the window you asked for.
The timestamps are now placed where the API actually reads them: per-site
beginTimestamp/endTimestampnested inside each entry of thesitesarray
(ISO 8601 UTC). Verified against the live API — a nested window is honored
exactly, whereas the old top-level form returned the full default range.list_hosts,list_sites, andlist_all_sites_aggregatednow aggregate
across all configured API keys instead of silently returning only the
first key's results in multi-key MSP deployments
(#19).
Multi-key results carry a top-levelkey_labelslist, annotate each record
with its source_keyLabel, and use partial-failure semantics: a single
failing key surfaces undererrorsrather than aborting the call;
all-keys-fail raises. Single-key deployments are unchanged.query_isp_metricsnow validates that asitesfilter is provided and raises
a descriptiveValueErrorif it is absent. Previously an unscoped call passed
through to the API and returned an opaque HTTP 400 error.- The server
instructionsblock advertisedupdate_radius_profileand
delete_radius_profilein its parameter-scope reference, but no such tools exist
and the Site Manager API exposes no RADIUS profile update or delete endpoint
(verified live: the/ea/radius-profilesresource is not routed). An agent that
read the instructions and called one got atool-not-foundwith no recovery path.
The two phantom references are removed and the RADIUS capability line now states
the real surface (list/get/create only). - Three tool parameters whose valid values were only implied by example are now
enumerated from the value set, each established against the live API:session_typeonlist_client_sessions:all(default; unfiltered),user,
andguestare the values that narrow the result. An unrecognised value is not
rejected and does not return an empty array — the endpoint silently ignores it
and returns the fullallset, so a typo yields everything rather than a visible
error. Documented explicitly to prevent a wrong value passing as "no data".order_directiononlist_recognition_groups:ascordesc, case-insensitive;
an unrecognised value is rejected upstream with HTTP 400. It takes effect only
together withorder_by, and defaults to descending when omitted.file_typeonlist_protect_files/upload_protect_file:soundsandimages
are the known asset categories; the GET endpoint does not validate the value (an
unknown category returns an empty list rather than an error), so the docstring now
states this rather than implying a wrong value would surface.