unifi-siem-lens
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@unifi-siem-lensCheck for pending security finding analyses and post a recommendation for the most critical one."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.

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 ofunifi-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 ( | One-off maintenance actions — currently just the rule-triage backfill (see below). |
| Liveness + sink DB / schema-contract status. |
| Re-checks every existing |
| List analysis requests queued from the dashboard, awaiting a recommendation. |
| Full context for one request: the finding, its trigger/baseline history, and the relevant raw events. |
| 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:
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).Operational noise — WAN/device-health telemetry, not a security signal.
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.
Homelab/local-service enrichment
An IDS finding pointing at 192.168.1.26:8989 means nothing to a Claude
Code session unless it also knows 192.168.1.26:8989 is your own Sonarr
instance, not a mystery host. HOMELAB_SERVICES_PATH points lens at a
local JSON file mapping LAN IPs to the services running on them (typically
the containers on a homelab/NAS box), so that context is attached
automatically whenever a finding involving that IP is queued for analysis.
This is deliberately not something you configure by pointing lens at your Docker Compose files or infrastructure repo directly — the file is a static, hand-maintained snapshot that lives outside git, so your homelab's layout never ends up in this (public) repo's history or a published Docker image.
Setup:
Copy the template:
cp server/homelab-services.example.json server/data/homelab-services.json(the defaultHOMELAB_SERVICES_PATH; set the env var if you'd rather keep it elsewhere).Fill in your own hosts and ports:
{ "192.168.1.26": { "label": "tranquility (homelab)", "notes": ["Plex remote access is in use on tranquility"], "services": [ { "port": 8989, "name": "sonarr", "description": "TV show PVR" }, { "port": 8080, "name": "qbittorrent", "description": "torrent client WebUI" } ] } }Keys are the destination IPs as they appear in
events.db;servicesis matched against each event'sdest_port. An IP present with no matching port still returns the hostlabel, so you get partial credit ("this is at least a known host") even for ports you haven't listed.notesis optional and free-text — use it for standing, host-wide facts that don't map to a single port (e.g. "Plex remote access is in use" or "this box is intentionally exposed to the internet via Cloudflare Tunnel"). Every note is attached to every event matching that host, regardless of which port it hit.Restart lens. The file is loaded once at startup — like
SINK_DB_PATH, a missing or malformed file is a silent no-op, never a startup failure.
What it affects: only the context bundle built when a finding is
queued via Analyze (POST /findings/:id/analyze) — each recent event
in that bundle gets a homelab: { host, service } field the analyzing
Claude Code session sees alongside the raw event. It has no effect on the
dashboard, the rule-based auto-triage layer, or findings already queued
before the file existed — re-run Analyze on a finding to pick up a
newer version of the file.
server/homelab-services.example.json (checked in) documents the shape
with placeholder values; server/data/homelab-services.json (gitignored,
matching the data/ pattern used for lens.db) is where your real
mapping goes — see Environment Variables below
for HOMELAB_SERVICES_PATH.
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_analysesqueue, 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 |
| yes | — | Path to |
| yes | — | Bearer token clients must send as |
| no |
| Port the dashboard/API/MCP server listens on |
| no |
| Bind address. Defaults to localhost-only — the dashboard/REST API have no authentication of their own (only |
| no |
| Lens's own SQLite store — findings, baselines, seen-entity tracking, analysis-request queue |
| no | (none) | Comma-separated CIDRs treated as internal/LAN for the internal-source heuristic, e.g. |
| no | (none) | Comma-separated admin display names (as they appear in UniFi's own audit log, e.g. |
| no |
| 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 |
| no | (unset) | Optional |
| no | (unset) | Bearer token sent as |
| no |
| Path to a local, never-committed JSON file mapping LAN host IPs to the services running on them (e.g. a homelab server's Docker containers — see |
| no |
|
|
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 --buildExposes 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 devStarts 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 serverfails to boot (a known issue on Node 24.x/25.x — seeunifi-siem-sink's CLAUDE.md for details), usenpm run build -w server && npm run start -w serverinstead, alongsidenpm run dev -w webfor the frontend.
Production build
npm run build
npm startBuilds 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(taggedlatest,{major}.{minor}, and{version})Creates a GitHub Release with auto-generated notes
Steps to release:
Ensure all changes are merged to
mainand CI is green.Decide the new version (follows semver:
MAJOR.MINOR.PATCH).Update
"version"inpackage.json(root) to the new version.Commit:
git commit -m "chore: release v{version}" package.jsonTag:
git tag v{version}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
This server cannot be deployed
Maintenance
Related MCP Connectors
Enable secure connectivity between Sentry issues and debugging data, and LLM clients, using a Model Context Protocol (MCP) server.
A paid remote MCP for AI SDK data query MCP, built to return verdicts, receipts, usage logs, and aud
A paid remote MCP for AI SDK eval dashboard, built to return verdicts, receipts, usage logs, and aud
Authenticated public evidence search, verification, research jobs, exports, and webhooks.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceProvides 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-
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage Kibana security alerts, rules, and exception lists via the Model Context Protocol.13MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP-capable clients to interact with UniFi Site Manager and UniFi Dream Machine telemetry, providing tools for client details, ISP metrics, and more.4MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI-powered security investigation and threat analysis by connecting MCP-compatible clients to Uptycs Juno.Apache 2.0