Skip to main content
Glama

UniFi SIEM Lens

Visualization dashboard and code-driven analysis layer for UniFi IDS/IPS security events — trend charts, anomaly/repeat-offender detection, and a "queue for Claude" handoff so a Claude Code session can turn a flagged finding into an actual recommendation, without this service needing any API key of its own.

UniFi SIEM Lens dashboard: KPI cards for open findings, critical severity, 24h events, and repeat offenders; trend charts for events over time, top signatures, top source IPs, and severity distribution; and a findings list with acknowledge/dismiss/analyze actions.

Why this exists

This is the third of three companion projects for running an LLM against a UDM Pro:

  • unifi-mcp-server — exposes the UniFi Network API (firewall rules, networks, clients, traffic rules, port forwarding, monitoring) as MCP tools.

  • unifi-siem-sink — listens for UniFi's SIEM/syslog export and stores IPS/IDS and Security-category events (the one thing the Network API doesn't expose) in SQLite, queryable over MCP.

  • unifi-siem-lens (this project) — sits on top of unifi-siem-sink's event store. Where the other two projects hand raw data to an LLM one query at a time, lens does the first pass itself: it runs scheduled, code-driven heuristics (new signature/source-IP detection, internal-source flagging, repeat-offender tracking, statistical anomaly detection) against the sink's event history, turns anything interesting into a finding, and renders trends and findings on a dashboard. When a finding is worth a closer look, one click queues it for analysis — a Claude Code session (running on your Claude subscription, no separate API key) checks in over MCP, reasons about the finding with the real events as context, and posts a recommendation back into the app. A second, purely code-driven layer goes further for the common case: a handful of rules (a trusted admin's own console login, WAN/operational noise, a blocked hit from a known reputation/blocklist IDS signature) auto-dismiss a finding the moment it's detected, with no Claude Code session involved at all — see Rule-based auto-triage below.

Run all three and an LLM gets full visibility into the network (via unifi-mcp-server), full visibility into the security event stream (via unifi-siem-sink), and a standing analyst that's already triaged the noise before you ever open a chat (via unifi-siem-lens).

Related MCP server: kibana-mcp

Quick Start (Docker)

Requires unifi-siem-sink already running as a container — lens reads its event database read-only via --volumes-from.

1. Create a .env file

SINK_DB_PATH=/data/events.db
LAN_CIDRS=10.0.0.0/8,192.168.0.0/16,172.16.0.0/12
MCP_SECRET=<choose-a-strong-secret>

SINK_DB_PATH=/data/events.db matches the mount point unifi-siem-sink uses internally — see step 2.

2. Run the container

docker run -d \
  --name unifi-siem-lens \
  --env-file .env \
  -p 3002:3002 \
  -v unifi-siem-lens-data:/lens-data \
  --volumes-from unifi-siem-sink:ro \
  ghcr.io/ianchesal/unifi-siem-lens:latest

--volumes-from unifi-siem-sink:ro mounts the sink container's /data volume (containing events.db) into this container read-only, at the same path. If your sink container has a different name (check with docker ps), or you're running it via docker compose under a different project name, adjust unifi-siem-sink above to match.

3. Open the dashboard

http://<homelab-ip>:3002

4. Add to Claude Code for the analysis handoff

{
  "mcpServers": {
    "unifi-siem-lens": {
      "type": "http",
      "url": "http://<homelab-ip>:3002/mcp",
      "headers": { "Authorization": "Bearer <your-MCP_SECRET>" }
    }
  }
}

Then, in a Claude Code session with this configured, ask it to check for pending analyses. It'll fetch any findings you've queued from the dashboard, reason about them with the actual event context, and post recommendations back — which then show up next to the finding.

Dashboard & MCP tools

Surface

What it gives you

Dashboard (/)

Events-over-time, top signatures, top source IPs, and severity-distribution charts; a findings list with status tabs (Active/New/Acknowledged/Dismissed/Resolved/All), an "Analyze all" button, and per-finding Acknowledge/Dismiss/Analyze actions. Each answered finding shows whether its recommendation came "via Claude Code" or was "auto-triaged by rule".

Admin (/admin)

One-off maintenance actions — currently just the rule-triage backfill (see below).

GET /health

Liveness + sink DB / schema-contract status.

POST /api/admin/backfill-rule-triage

Re-checks every existing new/acknowledged finding against the auto-triage rules; returns { checked, dismissed, byRule }.

get_pending_analyses (MCP)

List analysis requests queued from the dashboard, awaiting a recommendation.

get_analysis_context (MCP)

Full context for one request: the finding, its trigger/baseline history, and the relevant raw events.

submit_analysis (MCP)

Post a recommendation + risk level back for a pending request.

Rule-based auto-triage

Most findings a live network throws off don't need an LLM at all: your own console logins, the gateway's WAN-health blips, and routine reputation-list scans that UniFi already auto-blocked. Rather than queuing every one of these for a Claude Code session, lens checks each new new_signature/ new_source_ip finding against three rules the moment it's detected, and auto-dismisses it — with a canned, code-generated recommendation, no Claude Code session involved — if every event behind it is fully explained by one of them:

  1. Trusted admin login — a console login from an admin name in TRUSTED_ADMIN_NAMES (see Environment Variables below; empty by default, so this rule never fires unconfigured).

  2. Operational noise — WAN/device-health telemetry, not a security signal.

  3. Reputation/blocklist scan — a blocked hit from a known reputation-feed IDS signature family (SAFE_SIGNATURE_PREFIXES).

A finding that isn't fully explained by a rule (mixed events, an unrecognized signature, an untrusted admin name) is left alone exactly as before — it still needs a manual "Analyze this" and a Claude Code session.

Because this only runs at the moment a finding is first created, findings already sitting in the database before you configure TRUSTED_ADMIN_NAMES (or before you upgrade to a lens version that has this feature) never get a rule pass on their own. Visit /admin and click Run backfill to sweep the auto-triage rules over your existing new/acknowledged findings — safe to run repeatedly, it only ever touches findings still sitting at those two statuses.

Claude Code skill: analyzing findings

If you're working in this repo with Claude Code, server/.claude/skills/analyzing-findings/ ships a skill (scoped to the server workspace, since it drives the MCP analysis handoff) that automates the ad hoc "check for pending analyses" workflow beyond what a plain request to Claude Code does on its own:

  • Drains the entire get_pending_analyses queue, not just one request.

  • Enriches each finding by resolving source/destination IPs to hostnames via unifi-mcp-server's client list, when that server is configured and reachable (falls back to IP-only otherwise).

  • Cross-references findings in the same batch that share a root cause (same source IP or signature) so their recommendations note the connection instead of being analyzed in isolation.

  • After answering each finding, flags any that look like good candidates for a new rule-based auto-triage rule — a low-risk, fully-explained pattern that has recurred and isn't already covered by an existing rule — without writing the rule code itself; that stays a deliberate, reviewed change to ruleTriage.ts.

Ask a Claude Code session working in this repo to "reanalyze findings" or invoke it directly as /server:analyzing-findings.

Environment Variables

Variable

Required

Default

Description

SINK_DB_PATH

yes

Path to unifi-siem-sink's events.db, opened read-only

MCP_SECRET

yes

Bearer token clients must send as Authorization: Bearer <MCP_SECRET> to call /mcp

PORT

no

3002

Port the dashboard/API/MCP server listens on

HOST

no

127.0.0.1

Bind address. Defaults to localhost-only — the dashboard/REST API have no authentication of their own (only /mcp does, via MCP_SECRET), so widen this deliberately (e.g. to your LAN interface IP, or 0.0.0.0) only if you want the dashboard reachable from other devices

LENS_DB_PATH

no

./data/lens.db (/lens-data/lens.db in Docker)

Lens's own SQLite store — findings, baselines, seen-entity tracking, analysis-request queue

LAN_CIDRS

no

(none)

Comma-separated CIDRs treated as internal/LAN for the internal-source heuristic, e.g. 10.0.0.0/8,192.168.0.0/16

TRUSTED_ADMIN_NAMES

no

(none)

Comma-separated admin display names (as they appear in UniFi's own audit log, e.g. Ian C.) that auto-dismiss new_source_ip findings which are entirely explained by that admin logging into the UniFi console. Empty by default — this rule never fires until configured

SAFE_SIGNATURE_PREFIXES

no

ET DROP,ET CINS,ET TOR,ET COMPROMISED,ET DSHIELD

Comma-separated IDS/IPS signature prefixes (Emerging Threats' reputation/blocklist rule-family naming convention) that auto-dismiss a finding when every backing event is a blocked hit from one of these signature families. Prefixes are matched via SQL LIKE (case-insensitive; a configured prefix containing % or _ acts as a wildcard) — keep entries specific

UNIFI_MCP_SERVER_URL

no

(unset)

Optional unifi-mcp-server MCP endpoint, e.g. http://localhost:3000/mcp. When set, lens resolves source IPs to known client names and pulls a firewall-rule summary into analysis context. Left unset, this enrichment is skipped entirely — never required

UNIFI_MCP_SERVER_TOKEN

no

(unset)

Bearer token sent as Authorization: Bearer <token> when calling UNIFI_MCP_SERVER_URL, for unifi-mcp-server instances that require auth. Ignored if UNIFI_MCP_SERVER_URL is unset

LOG_LEVEL

no

info

error | warn | info | debug


Development (Running from a Repo Clone)

Setup

git clone https://github.com/ianchesal/unifi-siem-lens
cd unifi-siem-lens
npm install
cp server/.env.example server/.env
# edit server/.env: set SINK_DB_PATH to your unifi-siem-sink events.db, and MCP_SECRET to a strong secret
# (docker cp unifi-siem-sink-unifi-siem-sink-1:/data/events.db ./server/data/events.db)

Run with Docker Compose

docker compose up -d --build

Exposes 3002/tcp for the dashboard/API/MCP endpoint. Requires unifi-siem-sink running as a container named unifi-siem-sink (adjust docker-compose.yml's volumes_from if yours is named differently) — lens's own state persists in the lens-data named volume.

Run locally

npm run dev

Starts the server (http://localhost:3002) and the Vite dev server (http://localhost:5173, proxying /api to the server) together. The server's /mcp endpoint is served directly on port 3002 and is not proxied through Vite.

Note: If npm run dev -w server fails to boot (a known issue on Node 24.x/25.x — see unifi-siem-sink's CLAUDE.md for details), use npm run build -w server && npm run start -w server instead, alongside npm run dev -w web for the frontend.

Production build

npm run build
npm start

Builds both packages and serves the built dashboard + API + MCP endpoint from a single process on PORT (default 3002).

Tests

npm test          # server test suite (vitest)
npm run lint       # biome (server) + oxlint (web)

Cutting a release

Releases are tag-driven. Pushing a v* tag to GitHub triggers .github/workflows/release.yml, which:

  • Builds and pushes a Docker image to ghcr.io/ianchesal/unifi-siem-lens (tagged latest, {major}.{minor}, and {version})

  • Creates a GitHub Release with auto-generated notes

Steps to release:

  1. Ensure all changes are merged to main and CI is green.

  2. Decide the new version (follows semver: MAJOR.MINOR.PATCH).

  3. Update "version" in package.json (root) to the new version.

  4. Commit: git commit -m "chore: release v{version}" package.json

  5. Tag: git tag v{version}

  6. Push both: git push origin main && git push origin v{version}

The release workflow fires automatically on the tag push. No manual Docker build or GitHub Release creation needed.

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides AI assistants with access to UniFi Network and Protect infrastructure for managing devices, monitoring clients, analyzing network health, viewing camera snapshots, and getting optimization recommendations across multiple UniFi controllers.
    2
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to manage Kibana security alerts, rules, and exception lists via the Model Context Protocol.
    12
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables MCP-capable clients to interact with UniFi Site Manager and UniFi Dream Machine telemetry, providing tools for client details, ISP metrics, and more.
    4
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ianchesal/unifi-siem-lens'

If you have feedback or need assistance with the MCP directory API, please join our Discord server