UniFi Access MCP Server

Query and manage UniFi Access doors, credentials, visitors, policies and events from an MCP client

By Chris Kirby (individual) Python Local · stdio SecurityDevOps and monitoring

57 popularity score ★ 1.0k ↓ 3.6k/mo

Connect your client

Local (PyPI)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

It needs UNIFI_ACCESS_HOST, UNIFI_ACCESS_USERNAME, UNIFI_ACCESS_PASSWORD. Replace each <NAME> with your value.

Claude CodeTerminal
claude mcp add unifi-access -e UNIFI_ACCESS_HOST=<UNIFI_ACCESS_HOST> -e UNIFI_ACCESS_USERNAME=<UNIFI_ACCESS_USERNAME> -e UNIFI_ACCESS_PASSWORD=<UNIFI_ACCESS_PASSWORD> -- uvx unifi-access-mcp
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "unifi-access": {
      "command": "uvx",
      "args": [
        "unifi-access-mcp"
      ],
      "env": {
        "UNIFI_ACCESS_HOST": "<UNIFI_ACCESS_HOST>",
        "UNIFI_ACCESS_USERNAME": "<UNIFI_ACCESS_USERNAME>",
        "UNIFI_ACCESS_PASSWORD": "<UNIFI_ACCESS_PASSWORD>"
      }
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "unifi-access": {
      "command": "uvx",
      "args": [
        "unifi-access-mcp"
      ],
      "env": {
        "UNIFI_ACCESS_HOST": "<UNIFI_ACCESS_HOST>",
        "UNIFI_ACCESS_USERNAME": "<UNIFI_ACCESS_USERNAME>",
        "UNIFI_ACCESS_PASSWORD": "<UNIFI_ACCESS_PASSWORD>"
      }
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "unifi-access": {
      "type": "stdio",
      "command": "uvx",
      "args": [
        "unifi-access-mcp"
      ],
      "env": {
        "UNIFI_ACCESS_HOST": "<UNIFI_ACCESS_HOST>",
        "UNIFI_ACCESS_USERNAME": "<UNIFI_ACCESS_USERNAME>",
        "UNIFI_ACCESS_PASSWORD": "<UNIFI_ACCESS_PASSWORD>"
      }
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.unifi-access]
command = "uvx"
args = ["unifi-access-mcp"]

[mcp_servers.unifi-access.env]
UNIFI_ACCESS_HOST = "<UNIFI_ACCESS_HOST>"
UNIFI_ACCESS_USERNAME = "<UNIFI_ACCESS_USERNAME>"
UNIFI_ACCESS_PASSWORD = "<UNIFI_ACCESS_PASSWORD>"
Gemini CLITerminal
gemini mcp add --scope user -e UNIFI_ACCESS_HOST=<UNIFI_ACCESS_HOST> -e UNIFI_ACCESS_USERNAME=<UNIFI_ACCESS_USERNAME> -e UNIFI_ACCESS_PASSWORD=<UNIFI_ACCESS_PASSWORD> unifi-access uvx -- unifi-access-mcp
Local (container)

Add in one click

Opens the app, which asks before it adds the server. Then replace each <NAME> in its settings with your value.

Or add it by command or config

It needs UNIFI_ACCESS_HOST, UNIFI_ACCESS_USERNAME, UNIFI_ACCESS_PASSWORD. Replace each <NAME> with your value.

Claude CodeTerminal
claude mcp add unifi-access -e UNIFI_ACCESS_HOST=<UNIFI_ACCESS_HOST> -e UNIFI_ACCESS_USERNAME=<UNIFI_ACCESS_USERNAME> -e UNIFI_ACCESS_PASSWORD=<UNIFI_ACCESS_PASSWORD> -- docker run -i --rm -e UNIFI_ACCESS_HOST -e UNIFI_ACCESS_USERNAME -e UNIFI_ACCESS_PASSWORD ghcr.io/sirkirby/unifi-access-mcp:latest
Claude Desktopclaude_desktop_config.json
{
  "mcpServers": {
    "unifi-access": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "UNIFI_ACCESS_HOST",
        "-e",
        "UNIFI_ACCESS_USERNAME",
        "-e",
        "UNIFI_ACCESS_PASSWORD",
        "ghcr.io/sirkirby/unifi-access-mcp:latest"
      ],
      "env": {
        "UNIFI_ACCESS_HOST": "<UNIFI_ACCESS_HOST>",
        "UNIFI_ACCESS_USERNAME": "<UNIFI_ACCESS_USERNAME>",
        "UNIFI_ACCESS_PASSWORD": "<UNIFI_ACCESS_PASSWORD>"
      }
    }
  }
}
Cursor~/.cursor/mcp.json
{
  "mcpServers": {
    "unifi-access": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "UNIFI_ACCESS_HOST",
        "-e",
        "UNIFI_ACCESS_USERNAME",
        "-e",
        "UNIFI_ACCESS_PASSWORD",
        "ghcr.io/sirkirby/unifi-access-mcp:latest"
      ],
      "env": {
        "UNIFI_ACCESS_HOST": "<UNIFI_ACCESS_HOST>",
        "UNIFI_ACCESS_USERNAME": "<UNIFI_ACCESS_USERNAME>",
        "UNIFI_ACCESS_PASSWORD": "<UNIFI_ACCESS_PASSWORD>"
      }
    }
  }
}
VS Code.vscode/mcp.json
{
  "servers": {
    "unifi-access": {
      "type": "stdio",
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "UNIFI_ACCESS_HOST",
        "-e",
        "UNIFI_ACCESS_USERNAME",
        "-e",
        "UNIFI_ACCESS_PASSWORD",
        "ghcr.io/sirkirby/unifi-access-mcp:latest"
      ],
      "env": {
        "UNIFI_ACCESS_HOST": "<UNIFI_ACCESS_HOST>",
        "UNIFI_ACCESS_USERNAME": "<UNIFI_ACCESS_USERNAME>",
        "UNIFI_ACCESS_PASSWORD": "<UNIFI_ACCESS_PASSWORD>"
      }
    }
  }
}
Codex~/.codex/config.toml
[mcp_servers.unifi-access]
command = "docker"
args = ["run", "-i", "--rm", "-e", "UNIFI_ACCESS_HOST", "-e", "UNIFI_ACCESS_USERNAME", "-e", "UNIFI_ACCESS_PASSWORD", "ghcr.io/sirkirby/unifi-access-mcp:latest"]

[mcp_servers.unifi-access.env]
UNIFI_ACCESS_HOST = "<UNIFI_ACCESS_HOST>"
UNIFI_ACCESS_USERNAME = "<UNIFI_ACCESS_USERNAME>"
UNIFI_ACCESS_PASSWORD = "<UNIFI_ACCESS_PASSWORD>"
Gemini CLITerminal
gemini mcp add --scope user -e UNIFI_ACCESS_HOST=<UNIFI_ACCESS_HOST> -e UNIFI_ACCESS_USERNAME=<UNIFI_ACCESS_USERNAME> -e UNIFI_ACCESS_PASSWORD=<UNIFI_ACCESS_PASSWORD> unifi-access docker -- run -i --rm -e UNIFI_ACCESS_HOST -e UNIFI_ACCESS_USERNAME -e UNIFI_ACCESS_PASSWORD ghcr.io/sirkirby/unifi-access-mcp:latest

Settings 11

UNIFI_ACCESS_HOST Required: IP or hostname of the UniFi OS console (falls back to UNIFI_HOST).
UNIFI_ACCESS_USERNAME Required: Local admin username for the session path; can be left out if only UNIFI_ACCESS_API_KEY is used, which limits most changes.
UNIFI_ACCESS_PASSWORD Required: Password for that local admin account.
UNIFI_ACCESS_API_KEY Optional: Official UniFi Access API key; needed for visitor passes and usable alone for reads.
UNIFI_ACCESS_PORT Optional: Console HTTPS port, 443 by default.
UNIFI_ACCESS_VERIFY_SSL Optional: Verify the controller's certificate; false by default.
UNIFI_ACCESS_API_PORT Optional: Port of the Access API used with the key, 12445 by default.
UNIFI_POLICY_ACCESS_DOORS_UPDATE Optional: Set true to allow door lock and unlock; other UNIFI_POLICY_ACCESS_<CATEGORY>_<ACTION> gates work the same way.
UNIFI_ACCESS_TOOL_PERMISSION_MODE Optional: confirm (default, preview then confirm) or bypass.
UNIFI_TOOL_REGISTRATION_MODE Optional: lazy (default), eager or meta_only.
UNIFI_ACCESS_REDACT_SENSITIVE_FIELDS Optional: Set false to return raw credential tokens and PINs.

In a container, a setting goes in with -e NAME=value after docker run.

What it does

UniFi Access MCP Server connects an MCP client to a self-hosted Ubiquiti UniFi Access controller. An assistant can use it to list doors and their lock state, search the event history or the live buffer the controller feeds over its websocket, and review policies, schedules, users, hardware and credentials. When changes are permitted it can also unlock or lock doors, issue or revoke NFC, PIN and mobile credentials, create or cancel visitor passes, edit policies, reboot hubs and readers, and change per-device settings such as a reader's greeting.

It lives in the sirkirby/unifi-mcp monorepo next to separate Network and Protect servers, and ships on PyPI as unifi-access-mcp and as a container on GHCR. There are two ways in. An API key reaches the official Access API on port 12445. A local admin login instead opens a session on the UniFi OS console at port 443 and proxies requests through it. Most changes need that session, while visitor passes go through the API key.

The tool manifest in the repository counts 37 tools: 31 for Access and six helpers for discovery, indirect and batch calls, loading tools on demand and building a support bundle. The tool catalog page in its docs leaves out access_get_device_configs, access_update_device_config and access_get_support_bundle. In the default lazy mode only the helpers are registered at start and the Access tools load when asked for. UNIFI_TOOL_REGISTRATION_MODE=eager registers all of them up front.

One point its docs get wrong: permissions.md says Access denies every change by default, but the shared policy gate in unifi_core/policy_gate.py allows a change when no UNIFI_POLICY_* variable is set. What holds a change back by default is the preview step, which wants a second call with confirm=true. To block changes outright, set UNIFI_ACCESS_READ_ONLY=true or a false policy gate.

What it needs

RuntimePython 3.13+Docker
Runs onmacOS, Windows, Linux
UniFi Access on a UniFi OS consoleYour own instanceA local admin account for the session path, an Access API key, or both
Also
  • A network route from this machine to the console

Run it with uv (for uvx) on Python 3.13 or later, or with Docker. Its docs advise a local admin or service account on the console and warn against a Ubiquiti SSO cloud account. Tracing export under uvx or pip needs the otel extra, installed as unifi-access-mcp[otel]. The Docker image already carries it.

Limits

  • Limit Without a controller host it starts in setup mode, offering only the read-only access_setup_status tool and making no controller connection
  • Limit Each change returns a preview first and runs on a second call with confirm=true, unless UNIFI_ACCESS_TOOL_PERMISSION_MODE is bypass
  • Read only UNIFI_ACCESS_READ_ONLY=true refuses every change, and per the policy gate code any value it does not recognise also counts as on
  • Not covered Visitor pass tools need the Access API key, while policy edits, credential changes, reboots and door groups need the admin session
  • Size cap access_list_events returns 30 events by default and at most 5000 with limit
  • Limit A door unlock lasts 2 seconds unless duration is given
  • Limit Credential tokens and PIN codes come back as ***REDACTED*** unless UNIFI_ACCESS_REDACT_SENSITIVE_FIELDS=false
  • Limit HTTP transport is off by default and binds to 127.0.0.1:3002, with no authentication unless UNIFI_MCP_HTTP_AUTH_TOKEN is set
  • Limit It never loads a .env file, so settings must come from the client's env block, exported variables or Docker env_file
  • Not covered Its README points software that integrates over HTTP to the separate unifi-api-server in the same repository instead

The permissions docs disagree with the code on the default. permissions.md says Access denies all changes until enabled, and configuration.md still prints a YAML block with create, update and delete set to false. The bundled config.yaml carries no such block, and unifi_core/policy_gate.py returns allowed when no UNIFI_POLICY_* gate is set. A fresh install therefore permits changes behind the preview and confirm step only.

Who it suits

Suits administrators of a UniFi Access site who want to audit badge events, check credentials and policies, or hand out visitor passes from an assistant, with each change shown as a preview before it runs. It runs on your own machine against your own controller, so that machine must be on, or routed to, the controller's network. It is a community project, not a Ubiquiti product. Anyone who wants it to read only should set UNIFI_ACCESS_READ_ONLY=true, since changes are open by default. For networking or cameras the same repository ships unifi-network-mcp and unifi-protect-mcp, and services that call over HTTP are pointed to unifi-api-server.

Tools 37

25 only read, 10 change things, 2 can delete or overwrite. A ? marks an optional parameter.

access_list_doors
Lists doors with lock state and connection statuscompact?: boolean
access_get_door
Shows one door's configuration, lock state and attached devicesdoor_id: string
access_get_door_status
Returns only the lock and position state of one doordoor_id: string
access_list_door_groups
Lists door groups used in policies (admin session)
access_unlock_door changes
Unlocks a door for a number of seconds, 2 by defaultdoor_id: stringduration?: integerconfirm?: boolean
access_lock_door changes
Locks a door at oncedoor_id: stringconfirm?: boolean
access_list_events
Searches past access events by time, door, user or topicstart?: stringend?: stringdoor_id?: stringuser_id?: stringtopic?: stringlimit?: integer
access_get_event
Fetches one event by its IDevent_id: string
access_recent_events
Reads recent events from the in-memory websocket bufferevent_type?: stringdoor_id?: stringlimit?: integer
access_subscribe_events
Explains how to follow events through the server's event resources
access_get_activity_summary
Totals activity over a period, broken down by event typedays?: integerdoor_id?: string
access_list_policies
Lists access policies with their doors, schedules and user groups
access_get_policy
Shows the full settings of one policypolicy_id: string
access_list_schedules
Lists access schedules
access_update_policy changes
Changes a policy's name, doors, schedule, user groups or enabled flagpolicy_id: stringchanges: objectconfirm?: boolean
access_list_credentials
Lists NFC, PIN and mobile credentials with owner and status
access_get_credential
Shows one credential's detailscredential_id: string
access_create_credential changes
Issues a new NFC, PIN or mobile credential to a usercredential_type: stringcredential_data: objectconfirm?: boolean
access_revoke_credential deletes
Deletes a credential for goodcredential_id: stringconfirm?: boolean
access_list_visitors
Lists visitor passes from the Access API (API key)
access_get_visitor
Shows one visitor passvisitor_id: string
access_create_visitor changes
Creates a visitor pass for a time windowname: stringfirst_name?: stringlast_name?: stringvalid_from?: stringvalid_until?: stringaccess_start?: stringaccess_end?: stringemail?: stringphone?: stringcompany?: stringvisit_reason?: stringremarks?: stringconfirm?: boolean
access_delete_visitor deletes
Cancels a visitor pass, which the controller keeps as a cancelled recordvisitor_id: stringconfirm?: boolean
access_list_devices
Lists hubs, readers, relays and intercoms with state and firmwarecompact?: boolean
access_get_device
Shows one device's type, firmware, MAC and IPdevice_id: string
access_get_device_configs
Reads a device's per-device settings entriesdevice_id: string
access_update_device_config changes
Changes settings a device already has, such as the reader greetingdevice_id: stringupdates: objectis_camera?: booleanconfirm?: boolean
access_reboot_device changes
Restarts an Access devicedevice_id: stringconfirm?: boolean
access_get_system_info
Reports controller model, firmware, uptime and device counts
access_get_health
Checks the API-key and session connections
access_list_users
Lists users with their credentials and groupscompact?: booleanlimit?: integer
access_tool_index
Lists available tools, filtered by name, category or searchname?: stringcategory?: stringsearch?: stringinclude_schemas?: boolean
access_execute changes
Runs another tool by nametool: stringarguments?: object
access_batch changes
Starts several tool calls in parallel and returns job IDsoperations: array
access_batch_status
Reports the progress or result of batch jobsjobId?: stringjobIds?: array
access_load_tools changes
Registers chosen tools for direct calling in lazy modetools: array
access_get_support_bundle
Builds a sanitized diagnostics bundle for the user to reviewprobe?: enumresource?: string

Resources 2

access://events/stream
Recent events from the websocket buffer
access://events/stream/summary
Counts and state of the event buffer

Similar servers