🛡️ MCP Security Vision
Servidor MCP (Model Context Protocol) para videovigilancia inteligente en tiempo real: conecta cámaras de seguridad (IP por RTSP, analógicas por DVR o capturadora USB) con agentes de IA como Claude Desktop, Cursor, LangChain, AutoGen o tus propios bots.
YOLOv8 detecta objetos, face_recognition identifica personas conocidas y polígonos de intrusión vigilan zonas restringidas. Todo el historial se guarda en SQLite y se expone al agente como herramientas MCP.

Imagen generada por overlay.py con la cámara sintética (CAMERA_SOURCE=synthetic): objetos YOLO, rostro identificado y dos zonas restringidas, una de ellas en alerta (rojo).
📚 Índice
- Características
- Novedades de la v2.0 / v2.1
- Arquitectura
- Instalación
- Configuración de cámaras
- Variables de entorno
- Conexión con agentes de IA
- Herramientas MCP
- Zonas restringidas
- Base de datos SQLite
- Docker
- GitHub Actions y Docker Hub
- Desarrollo y pruebas
- Seguridad y privacidad
- Problemas frecuentes
- Roadmap
- Publicar en GitHub
- Contribuir
- Licencia
✨ Características
| 🎯 Detección de objetos | YOLOv8 (personas, autos, mascotas, mochilas, paquetes…) con confianza configurable |
| 🧑 Reconocimiento facial | Identifica personas conocidas (known_faces/) vs. desconocidos, con estabilización temporal anti-falsos positivos |
| 🚧 Zonas restringidas | Polígonos de intrusión normalizados (0..1), por cámara y por tipo de objeto, con tiempo de permanencia |
| 🗄️ Historial permanente | SQLite con eventos, zonas, personas y rutas de evidencia; consultas por fecha, cámara y tipo |
| 🎥 Multicámara | Varias cámaras definidas en JSON, cada una con su hilo, tracker y estado |
| 🧵 Multihilo con reconexión | Bucle de vídeo en segundo plano, backoff exponencial si se cae el RTSP, nunca bloquea MCP |
| 🖼️ Capturas para agentes multimodales | capture_snapshot devuelve una imagen JPEG (con cajas/zonas opcionales) |
| 🔔 Notificaciones | Webhook JSON opcional (n8n, Home Assistant, Slack, Telegram vía bot) para alertas |
| 🐳 Docker listo | Dockerfile multi-stage + docker-compose + publicación automática en Docker Hub |
| ✅ CI con GitHub Actions | Lint, 144 tests (matriz Python 3.10/3.11/3.12 × MCP SDK 1.x/2.x) y smoke test de la imagen |
| 🔒 Controles de seguridad | Credenciales solo en .env, contraseñas RTSP enmascaradas en logs, HTTP ligado a loopback con validación de Host/Origin, usuario no-root en Docker |
🆕 Novedades de la v2.0 / v2.1
v2.1.0 es la revisión de seguridad y robustez: HTTP en loopback con validación de
Host/Origin, cleanup_history con confirmación, esquema SQLite v2, backoff real de
cámara, inferencia serializada y 144 tests. Ver CHANGELOG.md.
v2.0.1 corrige la guía de problemas frecuentes tras verificarla contra el código (mensajes de consola reales,
RTSP_TRANSPORT, FACE_MODEL, validación de YOLO_IMGSZ, comprobación de puerto antes de cargar modelos)
y añade los scripts de publicación. Ver CHANGELOG.md.
La v2.0 incorporó las extensiones solicitadas:
- Base de datos SQLite para historial permanente (
mcp_security_vision/database.py)- Tablas
events,zones,people, índices y WAL para escrituras concurrentes. - Herramientas
get_security_report, filtros por horas/tipo ycleanup_historycon retención.
- Tablas
- GitHub Actions para pruebas y publicación en Docker Hub
.github/workflows/ci.yml: lint + tests + smoke test del contenedor..github/workflows/docker-publish.yml: build multi-arquitectura (amd64/arm64) y push con tags semánticos.
- Zonas restringidas (polígonos de intrusión) (
mcp_security_vision/zones.py)- Detección punto-en-polígono, tracker por IoU para no duplicar eventos, dwell time y evidencia fotográfica.
- Herramientas MCP
add_restricted_zone,list_restricted_zones,remove_restricted_zone,get_active_intrusions.
Y de la hoja de ruta v2.0 original:
- Soporte multicámara con
CAMERASen JSON ycamera_iden todas las herramientas. - Arquitectura desacoplada (paquete
mcp_security_vision/), lista para combinarse con otros servidores MCP (Telegram, WhatsApp, Notion…) sin tocar el código de visión, más webhook opcional para reaccionar a alertas.
🏗️ Arquitectura
┌──────────────────────────────┐
Claude Desktop │ │
Cursor / Cline ──┤ Servidor MCP │
LangChain/AutoGen│ server.py (13 tools + 1 │
HTTP / Docker ───┤ recurso security://status) │
└──────────────┬───────────────┘
│ (mismo proceso, sin bloqueo)
┌──────────────▼───────────────┐
│ VisionSystem │
│ cámaras · zonas · registro │
└──────┬─────────────┬─────────┘
┌──────────────────┼─────────────┼──────────────────┐
▼ ▼ ▼ ▼
┌────────────┐ ┌─────────────┐ ┌──────────┐ ┌────────────┐
│ CameraWorker│ │ Pipeline │ │ SQLite │ │ Snapshots │
│ (1 hilo por │ │ YOLO → IoU │ │ events │ │ alertas │
│ cámara) │ │ → caras → │ │ zones │ │ .jpg │
│ reconexión │ │ zonas → log │ │ people │ │ retención │
└────────────┘ └─────────────┘ └──────────┘ └────────────┘
│ │
│ └─────► Webhook JSON (opcional)
└── RTSP · USB · DVR · synthetic
Estructura del repositorio
mcp-security-vision/
├── mcp_security_vision/ # Paquete instalable
│ ├── server.py # Herramientas MCP + arranque (compat. SDK 1.x/2.x)
│ ├── system.py # Fachada de negocio (lo que usan las tools)
│ ├── camera.py # Hilo por cámara + reconexión + estado
│ ├── pipeline.py # Detección → tracking → caras → zonas → eventos
│ ├── detectors.py # YOLOv8 + utilidades de cajas
│ ├── faces.py # Registro y reconocimiento facial
│ ├── zones.py # Polígonos de intrusión + monitor
│ ├── tracker.py # Tracker IoU (IDs estables)
│ ├── database.py # SQLite: eventos, zonas, personas
│ ├── snapshots.py # Evidencias JPEG y retención
│ ├── overlay.py # Dibujo de cajas, rostros y zonas
│ ├── notifications.py # Webhook opcional en segundo plano
│ ├── synthetic.py # Cámara sintética para demos/CI
│ ├── fakes.py # Modelos simulados (CI, sin torch ni dlib)
│ └── config.py # Settings desde .env (+ multicámara JSON)
├── config/zones.example.json # Zonas iniciales (seed)
├── known_faces/ # Fotos de personas conocidas (nombre_apellido.jpg)
├── snapshots/ # Evidencias generadas
├── data/ # security.db (SQLite)
├── docs/
│ ├── TROUBLESHOOTING.md # Problemas frecuentes (verificados con tests)
│ ├── PUBLICAR_EN_GITHUB.md # Cómo subir el repo (manual, gh, script, agente)
│ └── demo_annotated.jpg # Captura anotada de ejemplo
├── scripts/
│ ├── check_setup.py # Diagnóstico de instalación
│ ├── draw_zones.py # Editor visual de polígonos
│ ├── configure_repo.py # Quita placeholders antes de publicar
│ └── publish_github.sh # Push seguro con verificación de secretos
├── tests/ # 144 tests (unitarios + integración MCP)
├── .github/workflows/ # ci.yml + docker-publish.yml
├── .github/ISSUE_TEMPLATE/ # plantillas de bug y propuesta
├── SECURITY.md · CONTRIBUTING.md # política de seguridad y guía de contribución
├── server.py # Atajo: python server.py
├── Dockerfile · docker-compose.yml · Makefile
├── requirements.txt · requirements-dev.txt · pyproject.toml
└── .env.example
🚀 Instalación
Requisitos
- Python 3.10, 3.11 o 3.12 (dlib todavía no soporta 3.13+)
cmakey compilador C++ (paradlib, que usaface-recognition)- Debian/Ubuntu:
sudo apt install build-essential cmake libopenblas-dev liblapack-dev libgl1 - macOS:
brew install cmake - Windows: Visual Studio Build Tools con la carga de trabajo Desarrollo para el escritorio con C++ + CMake
- Debian/Ubuntu:
- Opcional: GPU NVIDIA con CUDA para acelerar YOLO (
YOLO_DEVICE=0)
Pasos
git clone https://github.com/kfr0st/mcp-security-vision.git
cd mcp-security-vision
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # configura tus cámaras y rutas
python scripts/check_setup.py --read-frames
¿Sin cámara a mano? Prueba todo el flujo con la cámara sintética y modelos simulados:
VISION_FAKE_MODELS=1 CAMERA_SOURCE=synthetic python server.py --transport streamable-http --host 127.0.0.1O simplemente
make demo.
Instalación como paquete (opcional)
pip install ".[vision]" # código + YOLO + dlib
mcp-security-vision --check # comando instalado
📷 Configuración de cámaras
Todo se define en .env (nunca en el código). Ver .env.example para la lista completa.
Una sola cámara
# Webcam o capturadora USB (índice del dispositivo)
CAMERA_SOURCE=0
# Cámara IP / DVR por RTSP
# CAMERA_SOURCE=rtsp://usuario:clave@192.168.1.50:554/Streaming/Channels/101
Varias cámaras
CAMERAS={"entrada": "rtsp://admin:clave@192.168.1.50:554/Streaming/Channels/101", "patio": 0, "demo": "synthetic"}
# O con nombres visibles y cámaras deshabilitadas:
# CAMERAS=[{"id":"entrada","name":"Puerta principal","source":"rtsp://..."},
# {"id":"patio","name":"Patio trasero","source":0},
# {"id":"cochera","name":"Cochera","source":2,"enabled":false}]
Todas las herramientas aceptan camera_id; si se omite se usa la primera cámara habilitada.
URLs RTSP habituales
| Marca | URL del canal 1 (principal) | Substream (menos CPU) |
|---|---|---|
| Hikvision (cámara) | rtsp://user:pass@IP:554/Streaming/Channels/101 |
.../102 |
| Hikvision (DVR/NVR) | rtsp://user:pass@IP:554/Streaming/Channels/101 |
.../102 |
| Dahua | rtsp://user:pass@IP:554/cam/realmonitor?channel=1&subtype=0 |
subtype=1 |
| TP-Link Tapo | rtsp://user:pass@IP:554/stream1 |
stream2 |
| Reolink | rtsp://user:pass@IP:554/h264Preview_01_main |
_sub |
| Amcrest | rtsp://user:pass@IP:554/cam/realmonitor?channel=1&subtype=0 |
subtype=1 |
🎥 Cámaras analógicas (coaxial/BNC): sin cambiar una línea de código.
- Vía DVR/XVR (recomendado): conecta el DVR al router y usa su RTSP por canal.
- Vía capturadora RCA/BNC → USB (~10 USD): el sistema la ve como webcam
CAMERA_SOURCE=0/1.
⚙️ Variables de entorno principales
| Variable | Por defecto | Descripción |
|---|---|---|
CAMERA_SOURCE / CAMERAS |
0 |
Fuente única o JSON multicámara (RTSP, índice USB, synthetic) |
PROCESS_EVERY_N_FRAMES |
5 |
Analiza IA cada N fotogramas (sube si la CPU va justa) |
YOLO_MODEL |
yolov8n.pt |
yolov8n/s/m/l/x.pt (n = más rápido) |
YOLO_CONF |
0.5 |
Umbral de confianza de objetos |
YOLO_DEVICE |
(auto) | cpu, 0 (GPU CUDA), mps (Apple) |
FACE_TOLERANCE |
0.6 |
Distancia máxima para considerar la misma persona (menor = más estricto) |
FACE_MODEL |
hog |
hog (rápido, CPU) o cnn (más preciso, GPU) |
FACE_SCALE |
0.25 |
Escala a la que se buscan rostros (súbela si las caras son pequeñas) |
FACE_STABILIZER_WINDOW / ..._MIN_VOTES |
3 / 2 |
Anti-parpadeo del reconocimiento |
FACES_WITHOUT_PERSON |
false |
Buscar rostros incluso sin detección de "person" |
EVENT_COOLDOWN_SECONDS |
20 |
Anti-spam del mismo evento |
INTRUSION_COOLDOWN_SECONDS |
30 |
Anti-spam de reentradas a una zona |
PRESENCE_TIMEOUT_SECONDS |
2.5 |
Tiempo sin ver a alguien para considerar que salió de la zona |
SNAPSHOT_ON_ALERT |
true |
Guardar evidencia JPEG de cada alerta |
DB_PATH |
data/security.db |
Base de datos SQLite |
KNOWN_FACES_DIR |
known_faces |
Fotos de referencia |
SNAPSHOTS_DIR |
snapshots |
Capturas |
ZONES_SEED_FILE |
config/zones.example.json |
Zonas iniciales (solo se importan si no existen) |
RECONNECT_DELAY_SECONDS / MAX_... |
5 / 60 |
Backoff de reconexión RTSP |
RTSP_TRANSPORT |
tcp |
tcp (estable), udp o auto (deja que FFmpeg decida) |
MCP_TRANSPORT |
stdio |
stdio, sse o streamable-http |
MCP_HOST / MCP_PORT |
127.0.0.1 / 8000 |
Solo para transportes HTTP |
ALERT_WEBHOOK_URL |
(vacío) | Webhook JSON para alertas |
VISION_FAKE_MODELS |
false |
Modo demo/CI sin YOLO ni dlib |
🤖 Conexión con agentes de IA
Claude Desktop / Cursor (transporte stdio)
Edita claude_desktop_config.json (~/Library/Application Support/Claude/ en macOS,
%APPDATA%\Claude\ en Windows) y añade:
{
"mcpServers": {
"security-camera": {
"command": "/ruta/absoluta/a/mcp-security-vision/venv/bin/python",
"args": ["/ruta/absoluta/a/mcp-security-vision/server.py"],
"env": {
"CAMERA_SOURCE": "rtsp://usuario:clave@192.168.1.50:554/Streaming/Channels/101"
}
}
}
}
En Windows usa
C:\\ruta\\...\\venv\\Scripts\\python.exe. Los logs del servidor van a stderr, así que no rompen el protocolo stdio.
Clientes HTTP (Docker, remoto, agentes web)
docker compose up -d --build # endpoint: http://localhost:8000/mcp
# o en local (solo accesible desde tu máquina):
python server.py --transport streamable-http --host 127.0.0.1 --port 8000
Ejemplo mínimo con el SDK oficial de Python:
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async def main():
async with streamablehttp_client("http://localhost:8000/mcp") as streams:
read, write = streams[0], streams[1] # 3 valores en SDK 1.x, 2 en SDK 2.x
async with ClientSession(read, write) as session:
await session.initialize()
status = await session.call_tool("get_live_status", {})
print(status.content[0].text)
asyncio.run(main())
El SDK 2.x renombró algunas funciones; el servidor incluye un shim para funcionar con MCP SDK 1.x y 2.x sin cambios.
Prompts de ejemplo
- «¿Hay alguien en la entrada ahora mismo? ¿Es alguien conocido?»
- «Toma una captura anotada del patio y dime si el repartidor dejó un paquete en el suelo.»
- «Revisa los registros de las últimas 24 horas y dime a qué hora pasó Carlos.»
- «Define una zona restringida llamada "Caja fuerte" con el polígono [[0.6,0.4],[0.95,0.4],[0.95,0.9],[0.6,0.9]] en cam1.»
- «¿Hay alguna intrusión activa en este momento?»
- «Estoy frente a la cámara: regístrame como "Administrador".»
🧰 Herramientas MCP
| Herramienta | Parámetros | Devuelve |
|---|---|---|
list_cameras |
— | Cámaras, fuente enmascarada, estado y FPS |
get_live_status |
camera_id="" |
Objetos, rostros, zonas con intrusos y últimos eventos |
capture_snapshot |
camera_id="", annotate=false, save=false |
Imagen JPEG (con cajas y zonas si annotate=true); save=true la guarda además en snapshots/ |
get_security_logs |
limit=20, camera_id="", hours=0, event_type="" |
Historial SQLite filtrado |
get_security_report |
hours=24, camera_id="" |
Totales por tipo/cámara, rostros y zonas |
get_active_intrusions |
camera_id="" |
Personas/objetos dentro de zonas ahora |
list_known_people |
— | Personas registradas (foto + alta) |
register_current_face |
person_name, camera_id="" |
Guarda el rostro actual (exige 1 sola persona) |
forget_known_person |
person_name |
Elimina foto, memoria y fila SQLite |
list_restricted_zones |
camera_id="" |
Polígonos, etiquetas y estado |
add_restricted_zone |
camera_id, name, points, labels=["person"], enabled=true |
Crea/actualiza zona |
remove_restricted_zone |
camera_id, name |
Elimina zona |
cleanup_history |
keep_days=30, purge_snapshots=false, confirm=false |
Purga eventos antiguos (y capturas si purge_snapshots=true); exige confirm=true |
(recurso) security://status |
— | JSON con cámaras, personas, zonas y últimos eventos |
Eventos registrados (event_type)
intrusion · face_known · face_unknown · person_unidentified · camera_online · camera_offline ·
face_registered · face_forgotten · zone_added · zone_removed · system
🚧 Zonas restringidas (intrusión)
Las coordenadas son normalizadas (0..1) respecto al ancho y alto de la imagen:
(0,0) = esquina superior izquierda, (1,1) = inferior derecha. Así la zona funciona con 720p, 1080p o 4K
y sobrevive a reconexiones.
[
[0.35, 0.30],
[0.65, 0.30],
[0.65, 0.95],
[0.35, 0.95]
]
Cómo crear zonas
- Pidiéndoselo al agente (herramienta
add_restricted_zone). - Con el editor visual (recomendado para ajustar a mano):
python scripts/draw_zones.py --camera-id cam1 --name "Puerta trasera" --labels person,car # clic = vértice · u = deshacer · c = limpiar · s = guardar - Con el archivo semilla
config/zones.example.json(se importa al arrancar si la zona no existe).
Mecánica anti-spam
- El tracker asigna un ID estable (
#1,#2…) a cada objeto; solo se registra una intrusión por entrada. - Al salir de la zona (sin detección durante
PRESENCE_TIMEOUT_SECONDS) se libera el estado, y una nueva entrada vuelve a alertar (respetandoINTRUSION_COOLDOWN_SECONDS). - Cada intrusión guarda una captura de evidencia y se acompaña de
dwell_seconds(tiempo dentro).
🗄️ Base de datos SQLite
Archivo: data/security.db (WAL activado para escrituras concurrentes desde varios hilos de cámara).
-- Esquema resumido
events(id, camera_id, created_at, event_type, severity, description,
object_label, track_id, zone_names, snapshot_path, metadata)
zones(id, camera_id, name, points, labels, enabled, created_at, updated_at)
people(id, name, slug, file_name, created_at, updated_at)
Consultas útiles:
-- ¿Quién pasó hoy?
SELECT created_at, description FROM events
WHERE event_type = 'face_known' AND created_at >= date('now', 'start of day');
-- Intrusiones por zona en los últimos 7 días
SELECT zone_names, COUNT(*) FROM events
WHERE event_type = 'intrusion' AND created_at >= datetime('now', '-7 days')
GROUP BY zone_names;
-- Agenda de presencia por persona
SELECT json_extract(metadata, '$.person') AS persona, MIN(created_at), MAX(created_at), COUNT(*)
FROM events WHERE event_type = 'face_known' GROUP BY persona;
Las marcas de tiempo son hora local del servidor en ISO-8601 (YYYY-MM-DDTHH:MM:SS), ordenables como texto.
Respaldo: copia data/security.db (con WAL, usa sqlite3 data/security.db ".backup copia.db").
Retención: cleanup_history(keep_days=90, confirm=true); añade purge_snapshots=true
para borrar también las capturas. Sin confirm=true la herramienta solo describe lo que haría.
🐳 Docker
# 1. Configura el entorno
cp .env.example .env
# 2. Construye y levanta (endpoint MCP en http://localhost:8000/mcp)
docker compose up -d --build
docker compose logs -f
# 3. Prueba rápida sin cámara (modo demo)
docker run --rm -p 127.0.0.1:8000:8000 -e VISION_FAKE_MODELS=1 -e CAMERA_SOURCE=synthetic \
kfr0st/mcp-security-vision:latest
- Imagen multi-stage (dlib se compila en el builder; la imagen final no lleva compiladores).
- Corre como usuario no-root (
vision). - Volúmenes:
data(SQLite),snapshots,known_faces,config,yolo-models(pesos persistentes). - Para cámara USB en Linux añade
devices: ["/dev/video0:/dev/video0"]ygroup_add: ["video"]. - Por defecto solo se aceptan hosts locales (
localhost,127.0.0.1,[::1]y sus puertos). Si publicas detrás de un proxy, añade tu dominio aMCP_ALLOWED_HOSTS(por ejemplo,MCP_ALLOWED_HOSTS=mcp.midominio.com,mcp.midominio.com:*) y defineMCP_ALLOWED_ORIGINS=https://mcp.midominio.comsi usas un navegador. - El transporte HTTP no trae autenticación. No lo expongas directamente a Internet: usa un proxy con TLS y autenticación, o una red privada/VPN.
PRELOAD_MODELS=true(build-arg) incluyeyolov8n.pten la imagen para despliegues sin Internet.- El contenedor corre como UID 1000. Si usas bind mounts en Linux y ves
Permission denied:sudo chown -R 1000:1000 data snapshots known_faces(o usa volúmenes nombrados).
🔁 GitHub Actions y Docker Hub
CI (.github/workflows/ci.yml)
Se ejecuta en cada push y pull request:
- Lint:
ruff check+ruff format --check. - Tests: matriz Python 3.10 / 3.11 / 3.12 × MCP SDK 1.x / 2.x, con
VISION_FAKE_MODELS=1(sin torch ni dlib → CI en ~1 min), cobertura y JUnit XML. - Smoke test Docker: construye la imagen, arranca el contenedor en modo demo y espera a que abra el puerto 8000.
Publicación en Docker Hub (.github/workflows/docker-publish.yml)
Se dispara con tags v*.*.*, pushes a main o manualmente. Construye linux/amd64 + linux/arm64 y publica:
| Evento | Tags |
|---|---|
Tag v1.2.3 |
1.2.3, 1.2, 1, latest |
Push a main |
main, sha-xxxxxxx, latest |
Configuración (una sola vez)
- Crea un repositorio en Docker Hub:
kfr0st/mcp-security-vision. - Genera un Access Token en Docker Hub → Account settings → Security.
- En GitHub → Settings → Secrets and variables → Actions, añade:
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN- (opcional)
DOCKERHUB_REPOsi el repo no se llamamcp-security-vision.
- Publica:
git tag v2.1.0 && git push origin v2.1.0
Si faltan los secrets, el workflow solo hace build (con un warning) y no falla.
🧪 Desarrollo y pruebas
pip install -r requirements-dev.txt # sin torch/dlib
pip install --no-deps -e .
make test # pytest + cobertura (144 tests, ~25 s)
make lint # ruff check
make format # ruff format + fixes
make demo # servidor con cámara sintética y modelos falsos
make check # valida .env y rutas
Qué cubren los tests:
- Parseo de configuración (multicámara, validaciones, enmascarado de credenciales).
- SQLite: eventos, filtros temporales, resúmenes, zonas, personas y persistencia.
- Geometría de polígonos + monitor de intrusiones (entrada, dwell, reentrada, etiquetas).
- Tracker IoU, estabilizador de nombres, registro/reconocimiento facial (con backend falso).
- Pipeline completo con detectores simulados y evidencias JPEG.
- Webhook de notificaciones contra un servidor HTTP local.
- Integración MCP real: levanta
server.pyen un subproceso, lista herramientas, llama aget_live_status,add_restricted_zone,get_security_logs,capture_snapshot(verifica JPEG) y el recursosecurity://status.
server.pyse ejecuta en un subproceso durante los tests, por eso la cobertura global (~81 %) es menor que la de los módulos de lógica (~90 %); el arranque está cubierto end-to-end por el test de integración.
🔒 Seguridad y privacidad
- Las contraseñas de cámaras viven solo en
.env(ignorado por git) o en variables de entorno. - Los logs muestran las URLs con la clave y los tokens de la query enmascarados (
rtsp://admin:***@...). known_faces/,snapshots/ydata/están en.gitignore: no subas fotos de personas al repo.- Recomendado: usuarios RTSP de solo lectura y contraseñas fuertes; segmenta las cámaras en una VLAN.
- El reconocimiento facial biométrico es dato sensible: informa a las personas y cumple tu normativa local (GDPR/RGPD, LFPDPPP, Ley 29733 en Perú, etc.).
- En Docker el proceso corre sin privilegios y con
no-new-privileges. - El transporte HTTP escucha en loopback y valida
Host/Originpor defecto, pero no incluye autenticación: si lo expones, ponlo detrás de HTTPS + autenticación y limitaMCP_ALLOWED_HOSTS. cleanup_historyexigeconfirm=truey no borra capturas salvo que pidaspurge_snapshots=true.
Checklist para producción
-
MCP_HOST=127.0.0.1o proxy con TLS y autenticación. -
MCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINSajustados a tu dominio real. -
.envfuera de git y con permisos restringidos (chmod 600). - Usuario RTSP de solo lectura y cámaras en una VLAN segmentada.
- Retención definida (
cleanup_history) y copias de seguridad dedata/security.db. - Aviso de privacidad y base legal para las personas grabadas.
- Contenedor como usuario no-root y volúmenes con permisos mínimos.
- Dependencias auditadas (
pip-audit, Dependabot) y tokens rotados.
🩺 Problemas frecuentes
Guía completa y verificada ejecutando el código:
docs/TROUBLESHOOTING.md. Cada fila está cubierta por tests (tests/test_troubleshooting.py), así que no se queda desfasada.
| Síntoma | Causa / solución verificada |
|---|---|
pip install face-recognition falla compilando dlib |
face-recognition no tiene binarios: dlib se compila. Instala build-essential cmake python3-dev libopenblas-dev liblapack-dev (el paquete python3-dev es imprescindible y casi siempre se olvida). Windows: Build Tools C++ + CMake. Usa Python 3.10–3.12 (dlib falla en 3.13+). Alternativa sin compilar: imagen Docker o VISION_FAKE_MODELS=1 |
| La cámara entra en bucle de «sin señal» | El mensaje real es [cam1] Cámara SIN SEÑAL: … Reintentando en 5s... (no [WARN] Señal de cámara perdida). Comprueba URL/credenciales con ffplay -rtsp_transport tcp <url>, usa el substream (/102, stream2, _sub), revisa el canal del DVR y el códec (H.264 básico). El transporte es TCP por defecto (RTSP_TRANSPORT); si tu equipo necesita UDP: RTSP_TRANSPORT=udp. Diagnóstico: python scripts/check_setup.py --read-frames |
| La imagen va a tirones / con retraso | El análisis corre en el hilo de la cámara. Orden de impacto: FACES_ENABLED=false → FACE_SCALE=0.15 → PROCESS_EVERY_N_FRAMES=10 → YOLO_IMGSZ=480 (debe ser múltiplo de 32, se valida al arrancar) → substream → YOLO_DEVICE=0 (GPU) |
| No reconoce a nadie / dice «Desconocido» | Foto reciente, de frente, iluminada y con un solo rostro. FACE_TOLERANCE=0.65 ayuda, pero también: cara pequeña → FACE_SCALE=0.5; pocas lecturas → FACE_STABILIZER_MIN_VOTES=1; YOLO no ve «person» → FACES_WITHOUT_PERSON=true; FACE_MODEL=cnn (más preciso, lento en CPU). list_known_people avisa si una foto no tiene rostro detectable |
| Falsos positivos de personas | YOLO_CONF=0.6 y FACE_STABILIZER_MIN_VOTES=3 con FACE_STABILIZER_WINDOW=5 (si MIN_VOTES > WINDOW el arranque falla con ConfigError). No subas FACE_TOLERANCE en ese caso |
Address already in use |
Solo ocurre con sse/streamable-http (con stdio no hay puerto). Desde v2.0.1 el servidor comprueba el puerto antes de cargar los modelos y muestra cómo liberarlo o cambiarlo (MCP_PORT=8001) |
| Claude Desktop no conecta | Ruta absoluta al python del venv (.../venv/bin/python o ...\venv\Scripts\python.exe). Logs: macOS ~/Library/Logs/Claude/mcp.log y mcp-server-security-camera.log; Windows %APPDATA%\Claude\logs\. Prueba antes python server.py --check y reinicia la app por completo |
El contenedor no ve /dev/video0 |
Añade devices: ["/dev/video0:/dev/video0"] + group_add: ["video"]. Solo funciona en Linux: Docker Desktop en macOS/Windows no puede pasar dispositivos USB (usa RTSP) |
| La zona nunca dispara | El punto de referencia es el centro inferior de la caja (los pies), no la cabeza. Además: coordenadas 0..1, labels=["person"] (etiqueta exacta de YOLO, no «Persona»), zona enabled, camera_id correcto y respetar el cooldown de 30 s entre entradas. Verifica con capture_snapshot(annotate=true) |
Otros avisos de consola y qué significan
| Mensaje | Significado |
|---|---|
[cam1] Primer fotograma recibido: 1920x1080 (...) |
La cámara entrega vídeo ✅ |
[cam1] Cámara RECONECTADA (...) |
Se recuperó de una caída |
ConfigError: YOLO_IMGSZ debe ser múltiplo de 32 |
Resolución de inferencia inválida (usa 320/480/512/640) |
SDK sin soporte de transport_security |
Actualiza mcp a ≥1.9 |
⚠️ face_recognition no está instalado |
Falta dlib: instala requirements.txt o usa el modo demo |
🗺️ Roadmap
- Tracking con ByteTrack/BoTSORT para oclusiones y multitudes.
- Embeddings en SQLite (evitar re-calcular rostros al arrancar) y cifrado en reposo.
- Panel web (FastAPI + HTMX) con stream anotado y mapa de zonas.
- Notificaciones nativas a Telegram/WhatsApp como MCP aparte o plugin.
- Zonas por horario («solo de 22:00 a 06:00») y reglas de dirección (entrar vs. salir).
- Exportación de clips MP4 alrededor de cada alerta.
- Autenticación OAuth para el transporte HTTP.
📤 Publicar en GitHub
Guía paso a paso (manual, con gh, con script y prompt listo para el agente de opencode):
docs/PUBLICAR_EN_GITHUB.md
# 1) personaliza badges/URLs/autor con tus datos
python scripts/configure_repo.py --github-user kfr0st --name "kfr0st"
# 2) publica (aborta si detecta .env, *.db, *.pt o fotos de known_faces/)
bash scripts/publish_github.sh git@github.com:kfr0st/mcp-security-vision.git
🤝 Contribuir
Lee CONTRIBUTING.md para el entorno de desarrollo, estilo y comprobaciones antes del PR. Para vulnerabilidades usa el reporte privado de SECURITY.md; no abras un issue público.
- Haz fork y crea una rama:
git checkout -b feature/mi-mejora. make lint && make testen verde.- Pull request describiendo el cambio y, si aplica, nuevos tests.
📄 Licencia
MIT © 2026 kfr0st. Ver LICENSE.
Hecho con ❤️ para la comunidad MCP hispanohablante · modelcontextprotocol.io