使用说明
UniFi Protect MCP Server
An MCP (Model Context Protocol) server that exposes UniFi Protect's Integration REST API as tools for Claude Code and other MCP clients. Aligned with UniFi Protect API 7.3.70, with tools covering cameras, lights, sensors, chimes, viewers, sirens, fobs, relays, speakers, bridges, link stations, alarm hubs, arm profiles, live views, files, users, point-of-sale events, NVR status, and WebSocket subscriptions.
Prerequisites
- Node.js 22.x (22.19 or newer) or 24.x — see
enginesinpackage.json - A UniFi Protect system with the Integration API enabled
- An API key generated from your UniFi Protect console (see Choosing an API key)
Setup
Quick start (npx)
Add to Claude Code with a single command — no clone or build needed:
claude mcp add-json unifi-protect '{"command":"npx","args":["-y","@owine/unifi-protect-mcp@latest"],"env":{"UNIFI_PROTECT_HOST":"192.168.1.1","UNIFI_PROTECT_API_KEY":"your-api-key"}}' -s user
Use -s user for global availability across all projects, or -s project for the current project only.
A local console usually has a self-signed certificate, so the first call fails with a certificate error until you trust it: see Self-signed console certificates.
@latest runs the newest release every time the server starts, including a new major version. To take fixes but choose when to make breaking upgrades, pin the major version you have tested instead (@owine/unifi-protect-mcp@<major>).
From source
If you prefer to build locally:
git clone https://github.com/owine/unifi-protect-mcp.git
cd unifi-protect-mcp
corepack enable # provides pnpm at the version pinned in package.json
pnpm install
pnpm run build
This project uses pnpm — npm install ignores pnpm-lock.yaml and may resolve different dependency versions than CI.
Then add to Claude Code:
claude mcp add-json unifi-protect '{"command":"node","args":["/path/to/unifi-protect-mcp/dist/index.js"],"env":{"UNIFI_PROTECT_HOST":"192.168.1.1","UNIFI_PROTECT_API_KEY":"your-api-key"}}' -s user
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
UNIFI_PROTECT_HOST |
Yes | — | IP or hostname of your UniFi Protect console, optionally with :port. A leading https:// and trailing / are stripped; bracket IPv6 addresses |
UNIFI_PROTECT_API_KEY |
Yes | — | API key from the console's integration settings (see Choosing an API key). Surrounding whitespace is trimmed; a key with a control character inside is refused at startup |
UNIFI_PROTECT_CA_CERT |
No | — | Absolute path to a PEM file with the console's certificate, to verify a self-signed console without turning verification off (see below). Not allowed with api.ui.com or together with UNIFI_PROTECT_VERIFY_SSL=false |
UNIFI_PROTECT_VERIFY_SSL |
No | true |
Set to false (or 0/no/off, any case) to skip certificate verification for this server's connections to the console. Prefer UNIFI_PROTECT_CA_CERT. Not allowed with api.ui.com. Unrecognized values keep the default |
UNIFI_PROTECT_READ_ONLY |
No | true |
Set to false (or 0/no/off, any case) to enable write tools (default is monitoring-only mode). Unrecognized values keep the default |
UNIFI_PROTECT_CONSOLE_ID |
No | — | Cloud Connector console ID. Only used when UNIFI_PROTECT_HOST is api.ui.com; ignored (with a warning on stderr) otherwise |
The startup line in the MCP host's server log says whether the server is read-only or read-write. A configuration error names the variable at fault, and the server exits.
Self-signed console certificates
UniFi consoles ship with a self-signed certificate, so the first call to a local console usually fails with a certificate error. The safe fix is to trust that one certificate. Save it:
openssl s_client -connect 192.168.1.1:443 </dev/null 2>/dev/null | openssl x509 > ~/.config/unifi-protect-console.pem
Set UNIFI_PROTECT_CA_CERT to the file's absolute path (for example /home/you/.config/unifi-protect-console.pem); MCP hosts do not expand ~ or $HOME in env. The server then accepts exactly that certificate, even though it is issued to a name like unifi.local and you connect by IP. This also works for a certificate issued by a CA (one you installed on the console yourself): pinning the console's own certificate is enough, without the CA's. Any other certificate must chain to the file and match the hostname. If the network between you and the console is not trusted, compare the fingerprint (openssl x509 -in <file> -noout -fingerprint -sha256) with the console's. If the console later generates a new certificate, save it again.
Setting UNIFI_PROTECT_VERIFY_SSL=false instead turns certificate checks off for this server's connections to the console, and only those (other TLS connections in the process are unaffected). Anyone who can intercept that traffic can then read the API key, so use it only on a network you trust.
Choosing an API key
- Use the least-privileged key that works. Create the key from an admin account with no more access than you need. For the default read-only mode, that means an admin with view-only access to Protect; use a full-access admin only with
UNIFI_PROTECT_READ_ONLY=false. - Prefer a local console key to a Site Manager key. A Site Manager key (Cloud Connector,
api.ui.com) reaches every console on the account and travels over the internet.
Manual Configuration
Alternatively, add to your ~/.claude.json under the top-level "mcpServers" key, or to Claude Desktop's claude_desktop_config.json the same way:
{
"mcpServers": {
"unifi-protect": {
"command": "npx",
"args": ["-y", "@owine/unifi-protect-mcp@latest"],
"env": {
"UNIFI_PROTECT_HOST": "192.168.1.1",
"UNIFI_PROTECT_API_KEY": "your-api-key",
"UNIFI_PROTECT_CA_CERT": "/absolute/path/to/console.pem"
}
}
}
}
Cloud Connector
To reach a console through Ubiquiti's Cloud Connector instead of over the local
network, set UNIFI_PROTECT_HOST=api.ui.com and UNIFI_PROTECT_CONSOLE_ID to
the console's ID, and supply a Site Manager API key (unifi.ui.com →
Settings → API) as UNIFI_PROTECT_API_KEY. That is a different credential
from a local console API key. Requests are routed through
/v1/connector/consoles/{id}.
TLS verification stays on: the server refuses to start with
UNIFI_PROTECT_VERIFY_SSL=false or UNIFI_PROTECT_CA_CERT for api.ui.com.
Ubiquiti's cloud has a publicly trusted certificate, and a pinned certificate
would replace the public certificate authorities for this server's connections.
Find your console ID with:
curl -s https://api.ui.com/v1/hosts -H "X-API-KEY: $KEY" | jq -r '.data[].id'
WebSocket subscriptions do not work over Cloud Connector. The connector
proxies REST calls but returns HTTP 404 on a WebSocket upgrade, so
protect_subscribe_devices and protect_subscribe_events fail fast with an
explanatory error. Every other tool, including snapshots and file uploads,
works. Connect directly to the console host if you need subscriptions.
Note that /v1/hosts reports protect under controllers for consoles where
Protect is merely available, not necessarily installed; only consoles actually
running Protect will answer.
Setting UNIFI_PROTECT_CONSOLE_ID against a local host is not fatal: the
server logs a warning and talks to that host directly.
Upgrading to 3.0
3.0 puts more actions behind confirmation: physical outputs, disarming, anything that weakens alarm coverage, and live video and two-way audio. It also scopes TLS settings to this server, keeps every result within what one MCP message can carry, and makes cancellation stop the request to Protect.
If you use the server only through an LLM client (Claude Code, Claude Desktop, …), the model reads the new parameters from the tool schemas, so most setups need no change. Check three things:
- Cloud Connector with verification off no longer starts. Unset
UNIFI_PROTECT_VERIFY_SSLwhenUNIFI_PROTECT_HOST=api.ui.com. - Spellings of
UNIFI_PROTECT_READ_ONLY/UNIFI_PROTECT_VERIFY_SSLthat 2.x ignored now take effect (table below). - Expect more approval prompts. Update tools and every confirm-gated tool are now annotated destructive (below).
Read on if you call tools from scripts or saved agent configurations, or keep allow-lists keyed on tool annotations.
Check your environment variables first
2.x treated only the exact string false as false. 3.0 also accepts 0, no and off, in any case, so a value that 2.x silently ignored now takes effect:
| Setting | 2.x | 3.0 |
|---|---|---|
UNIFI_PROTECT_READ_ONLY=0 (or no, off, FALSE) |
Read-only | ⚠️ Write tools enabled |
UNIFI_PROTECT_VERIFY_SSL=0 (or no, off, FALSE) |
Certificate verified | Verification skipped |
UNIFI_PROTECT_HOST=api.ui.com with UNIFI_PROTECT_VERIFY_SSL=false (or 0/no/off) |
Verification skipped | ⚠️ Startup fails: "TLS verification cannot be disabled for api.ui.com". Unset UNIFI_PROTECT_VERIFY_SSL |
UNIFI_PROTECT_VERIFY_SSL=false |
Turned verification off for every TLS connection in the process | Applies only to this server's connections to the console. Prefer the new UNIFI_PROTECT_CA_CERT |
UNIFI_PROTECT_API_KEY pasted with a trailing newline or spaces |
Sent as is; every call failed and the error quoted the key | Trimmed |
UNIFI_PROTECT_API_KEY with a control character, or any character that is not printable ASCII (a zero-width space, a smart quote), inside |
Same | Startup fails with a message that does not repeat the key |
Any other unrecognized value keeps the safe default (read-only, verification on), as before. New: UNIFI_PROTECT_CA_CERT verifies a console's self-signed certificate (see Self-signed console certificates). Configuration errors now name the variable at fault, and the startup log line says whether the server is read-only or read-write.
Tool input changes (breaking)
| Tool | 2.x | 3.0 |
|---|---|---|
protect_disable_arm_alarm |
No confirmation | Requires confirm: true |
protect_activate_relay_output |
Omitting state toggled the output |
Requires confirm: true and state ("on" / "off") |
protect_trigger_alarm_hub_output |
Omitting enable toggled the output |
Requires confirm: true and enable (true / false) |
protect_create_rtsp_stream, protect_create_talkback |
No confirmation | Requires confirm: true; the returned URLs are credentials |
protect_stop_siren |
No confirmation | Requires confirm: true |
protect_delete_arm_profile, protect_set_current_arm_profile |
No confirmation | Requires confirm: true |
protect_update_sensor |
No confirmation | Requires confirm: true when settings include alarmSettings, motionSettings, glassBreakSettings, armProfileIds or scheduleMode |
protect_update_fob |
No confirmation | Requires confirm: true when settings include armControlSettings |
protect_update_arm_profile |
No confirmation | Requires confirm: true when settings include schedules, automations, activationDelay or recordEverything |
protect_update_siren |
No confirmation | Requires confirm: true when settings include volume |
| Every tool that takes an ID | Any string | Only IDs as list and get tools return them: letters, digits, - and _. An arm profile ID cannot be settings, enable or disable, in any case; an alarm webhook ID cannot be . or .. |
A call that leaves out state or enable, or passes a malformed ID, fails input validation. A call without confirm: true returns an error result ("This action requires confirmation: …"), so the agent can ask you and call again.
Toggling was removed because it is not safe to retry: a repeated toggle flips a gate or lock back to where it started. To act on the current state, read it first: each entry in outputs from protect_get_relay carries a state, and protect_get_alarm_hub reports alarmHub.output keyed by channel.
// 2.x: toggles output 0
{ "id": "<relay id>", "outputId": 0 }
// 3.0: explicit, so a repeat leaves the output as asked (a pulseDuration pulses again)
{ "id": "<relay id>", "outputId": 0, "state": "on", "confirm": true }
Relaxed: a dryRun: true preview never needs confirm. In 2.x, protect_disable_mic and protect_trigger_alarm_webhook rejected a dry run without confirm: true. Calls that send both still work.
New: every list tool except protect_list_nvrs takes optional limit and offset. A call with neither returns the whole list, as before.
Annotation changes (breaking for allow-lists)
protect_update_camera, everyprotect_update_<device>tool,protect_update_arm_profileandprotect_update_livevieware nowdestructiveHint: true, and so is every tool that takesconfirm(protect_stop_siren,protect_set_current_arm_profile,protect_create_rtsp_stream,protect_create_talkback,protect_disable_arm_alarm,protect_activate_relay_output,protect_trigger_alarm_hub_outputamong them). MCP clients that auto-approve non-destructive tools will now ask before running them, and allow-lists keyed on the annotation need updating.- Every tool now has a
titleandopenWorldHint: false.
Stricter validation
UNIFI_PROTECT_HOSTis parsed as a URL authority. A pastedhttps://192.168.1.1/now works, and:443is dropped. A path, credentials, a non-numeric or out-of-range port, or an unbracketed IPv6 address now fails at startup instead of at the first request. Write IPv6 as[2001:db8::1]or[2001:db8::1]:7443.protect_upload_filerejectsbase64Datathat is not valid base64 (bad characters, length or padding), and empty input, where 2.x uploaded corrupt bytes without an error. Adata:URL prefix and line breaks are accepted. A file too large to fit one MCP message is refused with "File too large to send in one MCP message"; in 2.x an oversized request closed the server's stdio transport and the server exited with nothing logged.protect_update_liveviewrejects a body whosesettings.iddiffers fromid.
Output changes
- Text results are compact JSON, not indented.
structuredContentis unchanged; use it if you parse results. - Single-object results and dry-run previews now pass MCP clients' output validation. In 2.x, a top-level field the schema did not list, or any dry-run preview of a tool with an output schema, failed with
-32602. - A snapshot, response or other result too large for one MCP message is refused with an error saying so. In 2.x it was sent, and the MCP client dropped the connection.
- The
protect_upload_filedry-run preview reportsbyteLength(decoded size) in place ofdataLength(base64 string length). protect_subscribe_devices/protect_subscribe_eventsstop at a message cap or a size budget, whichever comes first (both stated in each tool's description), and addtruncated: true.durationis the time actually spent listening. A listen that fails before any message arrives (refused or unauthorized socket) returns an error result; in 2.x it returned{messages: [], error}as a success. One that fails partway still returns what it collected, witherror.- A timeout names the call and what to do:
PATCH /cameras/…: Protect did not respond within … s, followed by "Retry when the console responds" for reads, or "The change may still have been applied; check before retrying" for writes. Snapshots and uploads time out before the MCP client's own request timeout, so you see this message instead of a bare client timeout. - Network and TLS errors name the underlying cause, e.g.
fetch failed: connect ECONNREFUSED …, instead of a barefetch failed. - A subscription that the console refuses, closes or drops before any message arrives returns an error naming why, instead of an empty list that reads as a quiet site. So does one whose window ends before the connection is established, or whose first message is too large to return.
- A list tool reads a response of up to several times one MCP message and returns the page asked for, so
limitandoffsetwork on a site whose full list would not fit. - Cancelling a tool call aborts its request to Protect, and a cancelled subscription closes its socket. In 2.x a cancelled relay switch could still land afterwards.
- The server reports its real package version to MCP clients; 2.x always reported
1.0.0. It also sends server instructions (see Safety Features).
For packagers
undici is a new runtime dependency (scoped TLS and connection keep-alive). Requests go through undici's own fetch rather than Node's, so:
- They speak HTTP/1.1, as before, and send
User-Agent: unifi-protect-mcp/<version>. - An HTTP proxy is used as Node's built-in
fetchwould: fromHTTPS_PROXY/HTTP_PROXY, minusNO_PROXY, only whenNODE_USE_ENV_PROXY=1or--use-env-proxyis set. A dispatcher installed with undici'ssetGlobalDispatcherno longer applies. The subscription WebSocket never used a proxy, and still does not.
Safety Features
- Tool annotations. Every tool declares
readOnlyHint,destructiveHintandopenWorldHint: false(each talks only to your console), plus a readabletitle, so MCP clients such as Claude Code can decide when to ask you.destructiveHint: truemarks every tool that deletes or replaces existing state, which includes everyprotect_update_*tool, and every tool that takesconfirm. Clients that auto-approve non-destructive tools ask before those. The tool tables below mark them DESTRUCTIVE. - Server instructions. On connect, the server gives the MCP host a short guide for the model, matched to read-only or read-write mode. It says to treat names and event text from the console as data, not instructions; that RTSPS and talkback URLs are credentials; and, when writes are enabled, to preview with
dryRunand ask you before passingconfirm. - Read-only mode. On by default. Only the read tools (list, get, snapshot, subscriptions) are registered; write tools do not exist for the model, and registering one in read-only mode fails at startup. Set
UNIFI_PROTECT_READ_ONLY=falseto enable writes. Read-only mode still returns RTSPS URLs fromprotect_get_rtsp_streams, and those are credentials for live video. - Confirmation parameter. Tools that are irreversible, switch physical outputs, trigger the alarm webhook, stop a siren, disarm or weaken alarm coverage, or open live video or two-way audio refuse to act unless the call carries
confirm: true. Without it the tool returns an error naming the action, so the agent can ask you first.protect_update_sensor,protect_update_fob,protect_update_sirenandprotect_update_arm_profileneed it only when the change touches alarm-related settings (each tool's description names them); renames and LED changes do not.confirmis filled in by the model, so it is a prompt for the model to ask you, not a lock; for a human gate, keep your MCP client's approval prompts on for destructive tools. - Dry-run support. Every write tool accepts
dryRun: trueand returns the request it would send (dryRun,action,path,body) without changing anything. A dry run never needsconfirm. - Request safety. IDs must be IDs returned by list and get tools, and a request path that would resolve outside the Integration API is refused before it is sent. Redirects are refused, so the API key is never forwarded. Cancelling a tool call aborts its request to Protect.
See SECURITY.md for reporting vulnerabilities and operating the server safely.
Structured output
Read tools, and write tools that return the resource, declare an outputSchema, and return structuredContent alongside compact JSON text. Schemas are published as loose objects, so a field a firmware update adds flows through instead of failing validation. A list's items are under result; a paged list also has totalCount and offset. protect_get_snapshot returns an image. A dry run returns {dryRun, action, path, body}. Action endpoints that return no body succeed with an empty result.
Tools
DESTRUCTIVE marks tools annotated destructiveHint: true. Requires confirm: true marks tools that take confirm; every one of them is also DESTRUCTIVE. Pages with limit/offset marks list tools that take them.
System
| Tool | Description |
|---|---|
protect_get_info |
Get system information and version details |
protect_list_nvrs |
Get the NVR console info (a single object) |
Subscriptions
| Tool | Description |
|---|---|
protect_subscribe_devices |
Subscribe via WebSocket to device state updates for a short window (not over Cloud Connector) |
protect_subscribe_events |
Subscribe via WebSocket to event notifications for a short window (not over Cloud Connector) |
Cameras
| Tool | Description |
|---|---|
protect_list_cameras |
List all cameras (pages with limit/offset) |
protect_get_camera |
Get camera details by ID |
protect_update_camera |
DESTRUCTIVE: Update camera settings |
protect_get_snapshot |
Get a JPEG snapshot (returns image) |
protect_create_rtsp_stream |
DESTRUCTIVE: Create an RTSPS stream session; the returned URLs are credentials for live video (requires confirm: true) |
protect_get_rtsp_streams |
Get active RTSPS stream sessions (the URLs are credentials for live video) |
protect_delete_rtsp_stream |
DESTRUCTIVE: Stop and delete an active RTSPS stream |
protect_create_talkback |
DESTRUCTIVE: Create a talkback (two-way audio) session; the returned URL is a credential (requires confirm: true) |
protect_disable_mic |
DESTRUCTIVE: Irreversibly disable the camera microphone; only a factory reset restores it (requires confirm: true) |
protect_start_ptz_patrol |
Start PTZ patrol at a given slot |
protect_stop_ptz_patrol |
Stop PTZ patrol |
protect_goto_ptz_preset |
Move PTZ to a preset position |
Lights
| Tool | Description |
|---|---|
protect_list_lights |
List all lights (pages with limit/offset) |
protect_get_light |
Get light details by ID |
protect_update_light |
DESTRUCTIVE: Update light settings |
Sensors
| Tool | Description |
|---|---|
protect_list_sensors |
List all sensors (pages with limit/offset) |
protect_get_sensor |
Get sensor details by ID |
protect_update_sensor |
DESTRUCTIVE: Update sensor settings (requires confirm: true to change alarm, motion or glass-break settings, arm profiles or schedule mode) |
Chimes
| Tool | Description |
|---|---|
protect_list_chimes |
List all chimes (pages with limit/offset) |
protect_get_chime |
Get chime details by ID |
protect_update_chime |
DESTRUCTIVE: Update chime settings |
Viewers
| Tool | Description |
|---|---|
protect_list_viewers |
List all viewers (pages with limit/offset) |
protect_get_viewer |
Get viewer details by ID |
protect_update_viewer |
DESTRUCTIVE: Update viewer settings |
Sirens
| Tool | Description |
|---|---|
protect_list_sirens |
List all sirens (pages with limit/offset) |
protect_get_siren |
Get siren details by ID |
protect_update_siren |
DESTRUCTIVE: Update siren settings: name, volume, LED (requires confirm: true to change the volume) |
protect_play_siren |
Activate the siren alarm for a given duration (5/10/20/30s) |
protect_stop_siren |
DESTRUCTIVE: Stop an active siren; it silences an alarm (requires confirm: true) |
protect_test_siren_sound |
Test the siren sound for 5 seconds at a given volume |
Fobs
| Tool | Description |
|---|---|
protect_list_fobs |
List all key fobs (pages with limit/offset) |
protect_get_fob |
Get fob details by ID |
protect_update_fob |
DESTRUCTIVE: Update fob settings (requires confirm: true to change arm control settings) |
Relays
| Tool | Description |
|---|---|
protect_list_relays |
List all relays (pages with limit/offset) |
protect_get_relay |
Get relay details by ID, including each output's state |
protect_update_relay |
DESTRUCTIVE: Update relay settings |
protect_activate_relay_output |
DESTRUCTIVE: Turn a relay output channel on or off, with optional pulse duration (requires confirm: true and an explicit state) |
Speakers
| Tool | Description |
|---|---|
protect_list_speakers |
List all speakers (pages with limit/offset) |
protect_get_speaker |
Get speaker details by ID |
protect_update_speaker |
DESTRUCTIVE: Update speaker settings (volume, mic) |
protect_test_speaker_sound |
Test the speaker sound at a given volume |
Bridges
| Tool | Description |
|---|---|
protect_list_bridges |
List all bridges (pages with limit/offset) |
protect_get_bridge |
Get bridge details by ID |
protect_update_bridge |
DESTRUCTIVE: Update bridge settings |
Link Stations
| Tool | Description |
|---|---|
protect_list_link_stations |
List all link stations, the non-alarm-hub gateways (pages with limit/offset) |
protect_get_link_station |
Get link station details by ID |
protect_update_link_station |
DESTRUCTIVE: Update link station settings |
Alarm Hubs
| Tool | Description |
|---|---|
protect_list_alarm_hubs |
List all alarm hubs (pages with limit/offset) |
protect_get_alarm_hub |
Get alarm hub details by ID |
protect_update_alarm_hub |
DESTRUCTIVE: Update alarm hub settings |
protect_trigger_alarm_hub_output |
DESTRUCTIVE: Turn an alarm hub output channel on or off: sirens, lights, locks (requires confirm: true and an explicit enable) |
Arm Profiles — local alarm manager
| Tool | Description |
|---|---|
protect_list_arm_profiles |
List all arm profiles (pages with limit/offset) |
protect_create_arm_profile |
Create a new arm profile |
protect_set_current_arm_profile |
DESTRUCTIVE: Set the active profile used when arming (requires confirm: true) |
protect_update_arm_profile |
DESTRUCTIVE: Update an arm profile (requires confirm: true to change schedules, automations, activation delay or recording) |
protect_delete_arm_profile |
DESTRUCTIVE: Delete an arm profile by ID (requires confirm: true) |
protect_enable_arm_alarm |
Arm the alarm using the current profile |
protect_disable_arm_alarm |
DESTRUCTIVE: Disarm the alarm (requires confirm: true) |
Live Views
| Tool | Description |
|---|---|
protect_list_liveviews |
List all live views (pages with limit/offset) |
protect_get_liveview |
Get live view details by ID |
protect_create_liveview |
Create a new live view |
protect_update_liveview |
DESTRUCTIVE: Replace a live view (send the full object from protect_get_liveview) |
Alarm & Files
| Tool | Description |
|---|---|
protect_trigger_alarm_webhook |
DESTRUCTIVE: Trigger an alarm webhook, firing its external alarm action (requires confirm: true) |
protect_list_files |
List device asset files (animations; pages with limit/offset) |
protect_upload_file |
Upload a device asset file (base64 in, sent as multipart/form-data) |
Users
| Tool | Description |
|---|---|
protect_list_users |
List Protect users, filtered by the key's access (pages with limit/offset) |
protect_get_user |
Get a Protect user by ID |
protect_list_ulp_users |
List UniFi Identity (ULP) users with enrolled credentials (pages with limit/offset) |
protect_get_ulp_user |
Get a UniFi Identity user by ID |
Point of Sale
| Tool | Description |
|---|---|
protect_ingest_pos_transaction |
Record a POS transaction as a camera event so it can be overlaid on recorded footage |
Troubleshooting
Configuration errors go to the MCP host's server log and the server exits. Tool errors reach the agent as results starting with Error:.
| Message | Cause and fix |
|---|---|
Configuration error: UNIFI_PROTECT_HOST is required / UNIFI_PROTECT_API_KEY is required |
Missing from the server's env. |
UNIFI_PROTECT_HOST must be a hostname or IP … |
A path, credentials or a bad port in the host; bracket IPv6 addresses. |
UNIFI_PROTECT_API_KEY contains a control character … |
Paste the key again. |
cannot read UNIFI_PROTECT_CA_CERT (…) |
The path does not exist for the server. Use an absolute path; ~ is not expanded. |
UNIFI_PROTECT_CA_CERT must name a PEM file containing a certificate |
No BEGIN CERTIFICATE block; run the openssl command again. |
…cannot be combined with UNIFI_PROTECT_VERIFY_SSL=false / TLS verification cannot be disabled for api.ui.com / UNIFI_PROTECT_CA_CERT is for local consoles |
Unset UNIFI_PROTECT_VERIFY_SSL (or UNIFI_PROTECT_CA_CERT for api.ui.com). |
Ignoring UNIFI_PROTECT_CONSOLE_ID (log line) |
The console ID only applies with UNIFI_PROTECT_HOST=api.ui.com. |
… (read-only: write tools are not registered) (log line) and no write tools |
Read-only is the default. Set UNIFI_PROTECT_READ_ONLY=false and restart the host. |
fetch failed: … The console's TLS certificate could not be verified … |
Save the console's certificate to UNIFI_PROTECT_CA_CERT. If you already did, the console has a new certificate: save it again. |
fetch failed: … The certificate for api.ui.com could not be verified … |
Something between you and Ubiquiti's cloud is presenting another certificate, such as a TLS-inspecting proxy or firewall. |
fetch failed: connect ECONNREFUSED …, ENOTFOUND |
Wrong or unreachable host. |
HTTP 401 / HTTP 403 |
Wrong or revoked key, or its admin lacks the rights. |
… Protect did not respond within … s |
The console is slow. For a write, check whether it was applied before retrying. |
… exceeds the …-byte limit / Result too large to send in one MCP message … |
The response is too large for one MCP message. Page a list with limit and offset; for a snapshot, drop highQuality. |
File too large to send in one MCP message |
Upload a smaller file. |
This action requires confirmation: … |
Expected: the action needs confirm: true, and the agent should ask you first. |
Use an ID returned by a list or get tool |
The agent passed a name or a malformed ID. |
Subscription failed: … |
The WebSocket was refused or unauthorized; this is not "no activity". |
WebSocket subscriptions are not available over Cloud Connector … |
Connect to the console directly for subscriptions. |
MCP error -32602 … Input validation error |
The call broke the tool's input schema (for example a missing state); the message names the field. |
Development
The Node version used for development is pinned in .nvmrc. With fnm installed, fnm use reads it automatically on cd.
pnpm run build # Compile TypeScript
pnpm start # Run the server
pnpm run typecheck # Type-check src/ and tests/ without emitting
pnpm run lint # ESLint, type-aware
pnpm run lint:fix # ESLint with auto-fix
pnpm test # Run all tests (vitest)
pnpm run test:watch # Run tests in watch mode
pnpm run test:coverage # Run tests with coverage
pnpm run test:package # Pack, install the tarball and smoke-test it over stdio
Git hooks are managed by lefthook — pnpm exec lefthook install enables them. Pre-commit runs ESLint on staged files, a full typecheck, and a GitGuardian secret scan when ggshield is installed (skipped otherwise), so fnm and pnpm both need to be on your PATH.
Commit conventions
This project uses conventional commits in pull request titles and release-drafter for releases. Pull requests are squash-merged, so the title is the commit subject:
feat: ...— new feature (minor version bump)fix: ...— bug fix (patch version bump)deps: ...— dependency update (patch version bump; Renovate's type for package bumps)feat!: ...— breaking change (major version bump); the!must be in the title, aBREAKING CHANGE:footer is not readchore:,docs:,ci:, etc. — no version bump
Every push to main updates a draft GitHub Release with the next version and its notes. A maintainer publishes the draft, which builds, tests and publishes that tagged commit to npm with provenance. To release a different version, edit the draft's tag and title before publishing. Release notes after 3.0.0 are in GitHub Releases; CHANGELOG.md covers 3.0.0 and earlier.
License
MIT
