vigi-nvr-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| VIGI_MCP_HOST | No | IP literal to bind for HTTP (keep it loopback) | 127.0.0.1 |
| VIGI_MCP_PORT | No | HTTP port | 8765 |
| VIGI_NVR_HOST | Yes | NVR hostname or IP (no scheme/port/path) | |
| VIGI_NVR_PORT | No | HTTPS API port | 443 |
| VIGI_NVR_FFMPEG | No | Path to ffmpeg if not on PATH (ffprobe is found beside it) | |
| VIGI_NVR_DRY_RUN | No | Writes return the exact request without sending it | false |
| VIGI_NVR_PASSWORD | Yes | Login password (1-128 chars) | |
| VIGI_NVR_USERNAME | No | Login username | admin |
| VIGI_MCP_LOG_LEVEL | No | Log level; logs go to stderr | INFO |
| VIGI_MCP_TRANSPORT | No | stdio or streamable-http | stdio |
| VIGI_NVR_RTSP_PORT | No | RTSP/ONVIF port probed by nvr_get_rtsp_status | 554 |
| VIGI_NVR_STATE_DIR | No | Persistent login-breaker state (per host, 0600) | ~/.local/state/vigi-nvr-mcp/ |
| VIGI_NVR_BACKUP_DIR | No | Where nvr_backup_config writes (dir/files 0600) | backups |
| VIGI_NVR_EXPORT_DIR | No | Clip/snapshot output (dir 0700) | ~/.local/share/vigi-nvr-mcp/exports |
| VIGI_NVR_VERIFY_TLS | No | NVRs ship self-signed certs, so off by default | false |
| VIGI_NVR_ALLOW_WRITES | No | First write gate (env). Writes also need confirm_write: true per call | false |
| VIGI_NVR_RTSP_PASSWORD | No | RTSP password; defaults to VIGI_NVR_PASSWORD | |
| VIGI_NVR_RTSP_USERNAME | No | RTSP account; defaults to VIGI_NVR_USERNAME | |
| VIGI_NVR_LOGIN_DISABLED | No | Freeze authentication before any network I/O | false |
| VIGI_NVR_TIMEOUT_SECONDS | No | Per-request timeout, 1-120 | 10 |
| VIGI_NVR_EXPORT_MAX_MINUTES | No | Longest export window, 1-1440 | 60 |
| VIGI_NVR_MAX_LOGIN_FAILURES | No | Per-host failure budget before the breaker trips, 1-5 | 1 |
| VIGI_NVR_EXPORT_RETENTION_DAYS | No | Age after which nvr_purge_exports deletes a clip, 1-3650 | 7 |
| VIGI_NVR_TLS_FINGERPRINT_SHA256 | No | Pin the cert by SHA-256 (64 hex, colons optional); fails closed on mismatch. Observe the value via nvr_status / --check-auth (tls_fingerprint_observed) |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| nvr_loginA | Explicitly log in to the NVR (exactly one attempt, never retried). Normally unnecessary: the first NVR tool call logs in. After any failed login, automatic logins stop; fix the cause before calling this. Once the server's failure budget is spent it refuses until restarted. |
| nvr_auth_statusA | NVR session state: authenticated, failed logins this process, the last failure's lockout counters and whether login is frozen. No network I/O. |
| nvr_get_device_infoB | NVR model, firmware and identity (read-only). |
| nvr_get_module_specC | Capability spec: max channels, codecs, feature flags (read-only). |
| nvr_get_system_infoB | System basics such as device name, time zone and session timeout (read-only). |
| nvr_get_network_infoB | NVR network configuration (read-only). |
| nvr_get_video_resolutionsB | Main/minor stream resolution tables (read-only). |
| nvr_callA | Catalogued gateway to any NVR call. Identify the call with (module, method, key) from nvr_list_calls/nvr_describe_call; pass the module body as params. Unknown calls are refused with the nearest matches. Anything the catalog flags as mutating, or whose method is not "get", needs VIGI_NVR_ALLOW_WRITES=true AND confirm_write=true. Set allow_extra=true to send keys outside the call's example. With VIGI_NVR_DRY_RUN the exact body is returned as data.request (and data.dry_run=true) and not sent. Credential fields in the reply are redacted. |
| nvr_raw_callA | Off-catalog escape hatch: send {"method": method, module: params} directly. Prefer nvr_call. Everything but method="get" needs both write gates; the login and user_management modules are always refused. Logged at WARNING. Honours VIGI_NVR_DRY_RUN. Reply credentials are redacted. |
| nvr_list_modulesA | List every API module the NVR exposes, with per-module call and mutating-call counts (read-only; no device I/O). |
| nvr_list_callsB | List the catalogued calls for one module (method, key, whether it mutates, an example and a response-shape hint). Read-only. |
| nvr_describe_callB | Describe one call by (module, method, key): its example parameters, whether it mutates and its response shape. Read-only. |
| nvr_list_channelsB | All bound NVR channels (chm added_dev). Credential fields always redacted. |
| nvr_get_channelB | One channel by id (credential fields redacted). |
| nvr_find_duplicate_channelsB | Group channels by device uuid and flag duplicates. Within a group, rows with online="0" or conn_status!="0" are marked stale (removal candidates). |
| nvr_plan_channel_cleanupA | Read-only. Produce an ordered, resumable cleanup plan: back up config, remove every ghost (one call each), re-read, then move each real camera stranded above slot 8 into the lowest confirmed-empty low slot. Each step lists the exact tool, arguments and the precondition to verify from the previous step; also returns a summary (counts, final layout) and an unsafe_if list of conditions that block moves (two real cameras share a uuid, more than 8 real cameras, an online ghost). Nothing is written; hand each step to the matching write tool yourself. |
| nvr_remove_channelA | Unbind one channel (chm_del_dev). DESTRUCTIVE. Requires VIGI_NVR_ALLOW_WRITES=true, confirm_write=true and expected_uuid equal to the live row's uuid. Refuses a row whose live online=="1" (a connected camera) unless force=true. Honours VIGI_NVR_DRY_RUN. Run nvr_backup_config first. Returns before/after rows. |
| nvr_move_channelA | Move a binding to another channel slot (chm_mod_dev_chn), keeping its credentials and settings. Refuses if new_id is occupied (the firmware would replace it). Same gates as nvr_remove_channel. Returns before/after rows. |
| nvr_list_disksB | Installed hard disks and their state (read-only, raw reply). |
| nvr_get_recording_statusB | Recording / storage policy status (read-only, raw reply). |
| nvr_get_storageB | Disks (status, capacity, free space, health) plus the overwrite and recording-plan policy (read-only). include_raw=true adds the raw reply at data.raw. |
| nvr_backup_configA | Download the NVR configuration backup to the server's backup directory (mode 0600). Read-only on the NVR; run this before any channel cleanup. |
| nvr_get_video_configA | Per-channel video stream configuration: main/minor resolution, codec and bitrate tables plus advanced encoder settings (read-only). Pass include_raw=true to also get the unprocessed device reply under data.raw. |
| nvr_get_image_configA | Image, OSD, privacy-mask (cover) and ROI configuration for one channel (1-16), read-only. include_raw=true adds the raw device reply at data.raw. |
| nvr_get_detection_configA | Detection configuration for one channel (1-16) and one detection kind (read-only). kind is one of: motion, people, vehicle, linecross, intrusion, region_entrance, region_exiting, loitering, abandon_and_taken, scene_change, audio_exception, tamper. include_raw=true adds the raw device reply under data.raw. Refuses an unknown kind or an out-of-range channel. |
| nvr_list_eventsB | Best-effort list of recent NVR events/alerts (disk, network, video-loss and similar), read-only. Pass since as an ISO-8601 timestamp to drop older events that carry a parseable time. include_raw=true adds the raw reply at data.raw. |
| nvr_get_usersA | NVR user accounts: names and groups only (credentials are never returned), read-only. include_raw=true adds the redacted raw reply at data.raw. |
| nvr_get_firewallA | Firewall configuration: allow/deny lists and protocol/service exposure (read-only). include_raw=true adds the raw reply at data.raw. |
| nvr_get_cloud_statusA | TP-Link cloud (TP-Link ID) binding and connection status (read-only). include_raw=true adds the raw reply at data.raw. |
| nvr_get_timeA | Device time, time zone, NTP and DST configuration (read-only). include_raw=true adds the raw reply at data.raw. |
| nvr_get_rtsp_statusA | Report whether ONVIF/RTSP is enabled (onvif_server.onvif.enabled) and whether the RTSP port (default 554, VIGI_NVR_RTSP_PORT) accepts TCP connections. Read-only. |
| nvr_enable_rtspA | Enable ONVIF/RTSP on the NVR (onvif_server set). One-time setup. WRITE: needs VIGI_NVR_ALLOW_WRITES=true and confirm_write=true. Honours VIGI_NVR_DRY_RUN. Re-reads and returns enabled_before/after. |
| nvr_get_stream_urlA | Redacted live RTSP URL for a channel (1-16), stream 1=main/2=sub, plus a hint naming the env vars that hold the credentials. The password is never returned. Read-only. |
| nvr_export_clipA | Export a replay window (ISO-8601 start/end, UTC offset required) to an mp4 in the export dir. By default uses ffmpeg -c copy (lossless). Writes to local disk, so it is gated like a write: VIGI_NVR_ALLOW_WRITES=true AND confirm_write=true. VIGI_NVR_DRY_RUN returns the argv with the URL redacted. Window <= VIGI_NVR_EXPORT_MAX_MINUTES. Set target_max_mb (e.g. 20 for Gmail) and/or max_width to re-encode with libx264 (CRF/scale chosen from duration and target) so the file fits; then also returns original_bytes, encoded_bytes and fits_target. Returns path, bytes, duration_s, sha256 and a redacted ffmpeg stderr tail. An empty window surfaces as NO_FOOTAGE_IN_WINDOW. |
| nvr_snapshotA | Capture one JPEG frame from a channel's live stream into the export dir (read-only; stream defaults to 2=sub). Needs RTSP enabled and ffmpeg available. Returns path, bytes and sha256. |
| nvr_list_exportsB | List mp4/jpg files in the export directory (name, bytes, mtime). Read-only. |
| nvr_delete_exportA | Delete one file from the export directory by name (path-confined; the name must be a generated export filename). WRITE: needs VIGI_NVR_ALLOW_WRITES=true and confirm_write=true. |
| nvr_list_recording_segmentsA | Typed recording timeline for one channel (1-16) on one day (YYYY-MM-DD), read-only: a list of {start, end, type, raw_type} where type is normal / motion / smart / manual / alarm / unknown. tz is 'local', 'utc' or an IANA zone. include_raw=true adds the raw device reply at data.raw. On an unrecognised reply shape returns PROTOCOL_ERROR with the payload under data.raw (the live run corrects the shape). |
| nvr_search_recordingsA | Alias of nvr_list_recording_segments (kept for compatibility). Lists recorded segments for one channel (1-16) on one day (YYYY-MM-DD), read-only. |
| nvr_list_motion_windowsA | Merged, de-duplicated activity windows for one channel (1-16) or "all", read-only. Combines the typed recording timeline with event logs, merging spans that overlap or sit within min_gap_s and dropping windows shorter than min_len_s. Provide date (YYYY-MM-DD) or since (ISO-8601); optionally narrow with since/until. kinds defaults to motion/smart/alarm. Returns sorted windows [{channel, name, start, end, duration_s, kinds, sources}] and a per-channel summary. |
| nvr_contact_sheetA | One JPEG contact sheet of cols x rows frames evenly sampled across a replay window (ISO-8601 start/end, UTC offset required), each tile's exact time burned in. Read-only (reads frames into the export dir). Returns the file info plus a tile->timestamp map so an agent can cite the moment. Needs RTSP enabled and ffmpeg available. |
| nvr_sample_framesA | Individual JPEG frames every every_s seconds across a replay window (ISO-8601 start/end, UTC offset required), up to max_frames. Read-only. Returns each frame's name, path and timestamp. Use after a contact-sheet hit to pin the exact moment. |
| nvr_get_exportA | Return one export file's content as base64 for files up to max_mb (default 20; Gmail caps attachments near 25 MB). Over the limit returns TOO_LARGE with the size and a hint to re-export with target_max_mb or fetch the exports:// resource. Path-confined to the export dir. |
| nvr_purge_exportsA | Delete exports older than VIGI_NVR_EXPORT_RETENTION_DAYS (default 7). Double-gated like any delete: needs VIGI_NVR_ALLOW_WRITES=true and confirm_write=true. VIGI_NVR_DRY_RUN=true lists what would be deleted without deleting. |
| nvr_statusA | Healthcheck: reachability, offered auth scheme, the device's lockout counters, local session state and safety policy. Never logs in. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 45 tools
Descriptions are detailed, but several clusters overlap: nvr_search_recordings is an explicit alias of nvr_list_recording_segments; nvr_get_storage subsumes nvr_list_disks and nvr_get_recording_status; nvr_get_device_info, nvr_get_system_info, and nvr_get_module_spec are adjacent info endpoints. The docs help, but misselection risk remains across the 45-tool surface.
All names use the nvr_ prefix and snake_case, with a mostly predictable verb_noun pattern (nvr_get_*, nvr_list_*, nvr_remove_channel, nvr_move_channel). Minor deviations like nvr_status, nvr_snapshot, nvr_contact_sheet, nvr_login, and nvr_call are still readable and consistent in style.
45 tools far exceeds the 25+ threshold for 'too many' and the surface feels over-expanded for the NVR domain. Many narrow read tools could be subsumed by the generic nvr_call gateway, and the explicit alias further inflates the count.
Read coverage is extensive and write paths exist for channel cleanup, RTSP enabling, export deletion/purging, and clip export. The nvr_call/nvr_raw_call gateways provide access to catalogued mutations for missing direct tools, though explicit add_channel, reboot, or firmware update are not surfaced as first-class tools.