Screen Control
Proporciona a los agentes de IA un uso seguro del ordenador en Windows: percepción de pantalla mediante OCR, fotogramas individuales y una transmisión MJPEG en vivo, además de control de ratón, teclado, ventanas, escritorio virtual y modo de juego con protección de seguridad, a través de una API HTTP local autenticada por token y MCP (16 herramientas).
Documentación
🖥️ Screen Control
Un sistema de control remoto local para agentes de IA: observa la pantalla de tu computadora en vivo y envía comandos de mouse/teclado hacia ella. Todo se ejecuta en tu propia máquina — ningún dato sale de ella, sin intermediarios en la nube.
Cada pulsación de tecla y cada captura de pantalla en este GIF pasaron por la API — el agente nunca tocó un teclado físico.
¿Por qué Screen Control?
Los agentes de IA de hoy pueden escribir código y llamar APIs — pero no pueden ver ni tocar tu escritorio. Screen Control le da a cualquier agente un uso general de computadora a través de una interfaz HTTP/MCP limpia y con protección de seguridad:
- Percibir — OCR para texto, fotogramas individuales o una transmisión MJPEG en vivo para modelos con capacidad de visión, y un endpoint de diferencias solo de texto para modelos que no pueden consumir imágenes en absoluto.
- Actuar — mouse absoluto y relativo, teclado seguro para Unicode, gestión de ventanas, control en segundo plano (sin foco), escritorios virtuales.
- Mantenerse seguro — autenticación por token, atajos peligrosos bloqueados, protección de foco, un vigilante de entrada atascada y un mecanismo de seguridad de emergencia se aplican en el servidor, sin importar cuán confundido esté el agente.
Un solo proceso, cero configuración, funciona con cualquier lenguaje que pueda hablar HTTP — o de forma nativa a través de MCP en Claude Desktop, Cursor, VS Code y agentes en la nube.
El Rendimiento Está Limitado por el Agente
Screen Control es la capa de percepción y actuación — los ojos y las manos. La velocidad y capacidad efectivas de cualquier agente que lo use están limitadas por ese agente mismo y por el entorno en el que se ejecuta:
- Velocidad de pensamiento — una acción por "turno" del agente: el bucle percibir → planificar → actuar → verificar vive en el agente, por lo que la latencia de inferencia del modelo y la profundidad de razonamiento marcan directamente el ritmo. La API en sí solo añade milisegundos por llamada.
- Capacidad de contexto — las lecturas de pantalla (texto OCR, fotogramas, diferencias) consumen la ventana de contexto del agente; una ventana más grande significa más conciencia situacional antes de que la verificación se degrade.
- Entorno de ejecución — la latencia de red, la sobrecarga de ida y vuelta MCP/HTTP, los límites de llamadas a herramientas y las restricciones de alojamiento se acumulan sobre el bucle.
En la práctica esto significa: el mismo repositorio hace que un modelo de razonamiento rápido sea rápido y capaz, y hace que un modelo lento sea lento — la cadena de herramientas no es el cuello de botella. Las tareas en tiempo real o con muchas acciones necesitan un agente con inferencia rápida y latencia de bucle de herramientas ajustada; los agentes más lentos deberían preferir tareas deliberadas y con mucha verificación.
Tabla de Contenidos
- ¿Por qué Screen Control?
- Características
- Arquitectura
- Instalación
- Inicio Rápido
- Referencia de la API
- 🤖 Para Agentes de IA
- Soporte MCP (Agentes en la Nube con Un Clic)
- Modelo de Seguridad
- Guía del Modo Juego
- Guía de Acceso de Visión
- Solución de Problemas
- Pruebas
- Contribuciones
- Licencia
Características
| Característica | Descripción |
|---|---|
| 🖼️ Transmisión de pantalla en vivo | Captura de pantalla que se actualiza continuamente en el navegador |
| 🖱️ Control de mouse | Clic, clic derecho, doble clic, desplazamiento, arrastrar y soltar mediante captura en vivo |
| ⌨️ Control de teclado | Escritura de texto (incluye Unicode/Turco, independiente del diseño), teclas y atajos (Ctrl+C, Alt+Tab…) |
| 👁️ OCR | Convierte el texto en pantalla a formato legible por máquina |
| 📷 Acceso de visión | Rutas de píxeles crudos para modelos con capacidad de imagen: fotogramas individuales, transmisión MJPEG, detección de movimiento basada en texto |
| 🪟 Gestión de ventanas | Listar, enfocar, cerrar de forma segura (WM_CLOSE), matar (estilo administrador de tareas) |
| 🖥️ Control sin foco | Leer/escribir ventanas en segundo plano mediante PostMessage sin robar el foco |
| 🎮 Modo juego | Mirada de cámara mediante movimiento relativo del mouse, teclas de mantener presionado para moverse |
| 🔐 Autenticación por token | Cada solicitud requiere X-Auth-Token (protección CSRF) |
| 🦺 Vigilante de entrada atascada | Libera automáticamente las teclas mantenidas después de 30 s de inactividad |
| 🛟 Mecanismo de seguridad | El cursor en la esquina superior izquierda aborta todos los comandos (deshabilitado en modo juego) |
Arquitectura
┌─────────────────────────────────────────────────────────┐
│ Browser (Web UI) │
│ ┌──────────┐ ┌──────────┐ ┌────────────────────────┐ │
│ │ Live │ │ Control │ │ Windows / Game Mode │ │
│ │ View │ │ Panel │ │ Panel │ │
│ └────┬─────┘ └────┬─────┘ └───────────┬────────────┘ │
│ │ │ │ │
└───────┼──────────────┼─────────────────────┼──────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ HTTP API (Flask) │
│ 127.0.0.1:8745 │
│ │
│ /api/screenshot /api/mouse /api/key │
│ /api/vision/* /api/ocr /api/window │
│ /api/game /api/held /api/release_all │
│ /api/windows /api/desktops /api/desktop │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Auth Layer │ │ Watchdog │ │ OCR Engine │ │
│ │ (token) │ │ (30s auto) │ │ (RapidOCR) │ │
│ └─────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ control.py (Core) │
│ │
│ Screen: mss (fast capture), PIL (processing) │
│ Mouse: pyautogui (absolute), SendInput (relative) │
│ Keyboard: pyautogui + SendInput+KEYEVENTF_UNICODE │
│ Windows: Win32 API (EnumWindows, SetForegroundWindow) │
│ Background: PrintWindow (capture), PostMessage (input) │
│ Virtual Desktops: pyvda │
│ Game Mode: ClipCursor + MOUSE_MOVE_RELATIVE │
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ backends/ (pluggable) │
│ WindowsBackend │ LinuxBackend │ MacOSBackend│
│ (full) │ (stub) │ (stub) │
└──────────────────────────────────────────────┘
Coordenadas y Concurrencia
Conciencia de DPI por monitor. control.py llama
SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2) en el momento de la importación —
antes de la importación de pyautogui, porque pyautogui toca las APIs de coordenadas
durante la importación y de lo contrario bloquearía el proceso al valor predeterminado
del manifiesto del intérprete (consciente del sistema). Con PMv2 activo, cada coordenada en el
sistema es un píxel físico de principio a fin: captura mss, cuadros delimitadores de OCR,
clics de pyautogui/SendInput, ClipCursor. En pantallas de alto DPI (escalado 125%/150%)
nada se desvía entre lo que informa el OCR y dónde hace clic el mouse.
Arquitectura de bloqueos. El servidor usa dos bloqueos independientes en lugar de un solo bloqueo global:
| Bloqueo | Protege | Endpoints |
|---|---|---|
_input_lock | mouse, teclado, modo juego, operaciones de ventana | /api/mouse, /api/key, /api/game, /api/window/post, ... |
_read_lock | captura, OCR, visión, enumeración | /api/screenshot, /api/ocr, /api/vision/*, /api/windows, ... |
Un OCR lento (3–5 s en una pantalla ocupada) ya no congela las lecturas concurrentes de captura o visión — las lecturas se ponen en cola detrás de lecturas, las entradas detrás de entradas.
Principio de Funcionamiento del Bucle en Vivo
Este sistema está diseñado para un bucle de percibir-actuar en vivo, no para cadenas de comandos preescritas:
- LEER — OCR o visión lee la pantalla antes y después de cada acción
- UNA ACCIÓN — cada ronda envía un solo comando
- VERIFICAR — el criterio de aceptación es "apareció en pantalla", no "lo envié"
- ADAPTAR — si la verificación falla, el siguiente paso cambia según lo que realmente se ve
Esto se aplica mediante la protección expect_hwnd: la escritura se rechaza (409) si
la ventana en primer plano no coincide con el objetivo.
Instalación
cd screen-control
pip install -r requirements.txt
Requisitos
| Paquete | Propósito | ¿Requerido? |
|---|---|---|
mss | Captura de pantalla rápida | ✅ Sí |
pyautogui | Control de mouse/teclado | ✅ Sí |
pyvda | Gestión de escritorios virtuales | ✅ Sí |
flask | Servidor HTTP | ✅ Sí |
Pillow | Procesamiento de imágenes | ✅ Sí |
rapidocr-onnxruntime | OCR (lectura de texto en pantalla) | ⚠️ Opcional |
Nota: El paquete de OCR es grande y puede tardar en instalarse. Si falla, todo lo demás sigue funcionando — solo la función de OCR no estará disponible.
Requisitos del Sistema
- SO: Windows 10/11 (x64)
- Python: 3.10+
- Pantalla: Cualquier resolución; el sistema se adapta automáticamente
Inicio Rápido
# 1. Start the server
cd screen-control
python server.py
# 2. Open in browser
# http://127.0.0.1:8745
# 3. Or control via API
TOKEN=$(cat .token)
curl -H "X-Auth-Token: $TOKEN" http://127.0.0.1:8745/api/screenshot -o screen.jpg
Referencia de la API
Capacidades
Devuelve el nombre del backend activo y lo que puede hacer. Los agentes deberían llamar esto primero (consulta ROADMAP.md para el plan multiplataforma).
GET /api/capabilities
→ {"ok": true, "backend": "windows",
"capabilities": {"screen_capture": true, "game_mode": true, ...}}
Valores de características: true (compatible), false (ausente), null (desconocido —
backend stub), "optional" (depende de una dependencia opcional).
Matriz de Soporte de Plataformas
| Capacidad | Windows | Linux X11 | Linux Wayland | macOS |
|---|---|---|---|---|
| Captura de pantalla | Completa | Completa | Dependiente del portal | Permiso requerido |
| OCR | Completa/opcional | Completa/opcional | Completa/opcional | Completa/opcional |
| Control de mouse | Completo | Completo | Restringido | Permiso de accesibilidad |
| Control de teclado | Completo | Completo | Restringido | Permiso de accesibilidad |
| Enumeración de ventanas | Completa | Dependiente del WM | Limitada | Dependiente de accesibilidad/API |
| Entrada en segundo plano | Fuerte | Dependiente del WM/aplicación | Generalmente no disponible | Limitada |
| Escritorios virtuales | Compatible | Dependiente del DE/WM | Dependiente del DE/WM | Específico de Spaces |
| Modo juego | Compatible | Experimental | Limitado | Experimental |
Los backends de Linux y macOS son actualmente stubs con cierre ante fallos: cada operación devuelve
BACKEND_UNAVAILABLE(501) hasta que se implemente (Fases 5–7 de la hoja de ruta). Windows es el backend de referencia.
Autenticación
Cada solicitud debe incluir el encabezado X-Auth-Token. El token se
genera en cada inicio del servidor y se escribe en .token.
TOKEN=$(cat .token)
| Código | Significado |
|---|---|
| 401 | Token faltante o inválido |
| 415 | POST sin Content-Type: application/json |
Arranque del token (para la interfaz web incluida):
GET /token
→ {"ok": true, "token": "abc123..."}
El endpoint
/tokenes seguro: la Política de Mismo Origen evita que páginas externas lo lean.
Captura de Pantalla
GET /api/screenshot
Devuelve una captura de pantalla en JPEG.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
monitor | int | 1 | Índice del monitor |
region | string | — | Subregión x,y,w,h |
curl -H "X-Auth-Token: $TOKEN" -o screen.jpg http://127.0.0.1:8745/api/screenshot
curl -H "X-Auth-Token: $TOKEN" "http://127.0.0.1:8745/api/screenshot?region=0,0,800,600"
GET /api/info
Devuelve las dimensiones de la pantalla y el estado del sistema.
{"ok": true, "width": 1920, "height": 1080, "ocr_available": true,
"failsafe": true, "game_mode": false}
Control de Mouse
POST /api/mouse
| acción | Parámetros requeridos | Parámetros opcionales | Descripción |
|---|---|---|---|
move | x, y | duration (predeterminado 0.15) | Mover el cursor a una posición absoluta |
click | x, y | button (izquierdo/derecho), clicks (predeterminado 1) | Hacer clic en una posición |
scroll | clicks | x, y | Rueda de desplazamiento (positivo=arriba) |
drag | x1, y1, x, y | duration, button | Arrastrar entre dos puntos |
down | button (predeterminado "izquierdo") | — | Mantener presionado el botón del mouse |
up | button (predeterminado "izquierdo") | — | Soltar el botón del mouse mantenido |
# Click at center of screen
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"click","x":960,"y":540,"button":"left"}'
# Right-click
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"click","button":"right","x":960,"y":540}'
# Scroll down
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"scroll","clicks":-3}'
Control de Teclado
POST /api/key
| acción | Parámetros requeridos | Descripción |
|---|---|---|
press | key | Presionar y soltar una tecla |
down | key | Mantener una tecla presionada (registrada para el vigilante) |
up | key | Soltar una tecla mantenida |
hotkey | keys (matriz) | Combinación de teclas (p. ej., ["ctrl","c"]) |
type | text | Escribir texto (Unicode, independiente del diseño) |
| Parámetro opcional | Predeterminado | Descripción |
|---|---|---|
expect_hwnd | — | Identificador de ventana para verificar el foco (409 si no coincide) |
interval | 0.03 | Retraso entre caracteres para type |
# Press Enter
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"press","key":"enter"}'
# Ctrl+C
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"hotkey","keys":["ctrl","c"]}'
# Type text (Turkish characters supported)
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"type","text":"Merhaba dünya"}'
# Hold W key down (for walking in games)
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","key":"w"}'
OCR (Lectura de Pantalla)
POST /api/ocr
Convierte el texto en pantalla a formato legible por máquina.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
region | matriz | — | Subregión [x, y, w, h] (más rápido) |
{
"ok": true,
"text": "Hello World\nFile Edit View",
"lines": ["Hello World", "File Edit View"],
"items": [
{"text": "Hello World", "x": 960, "y": 40},
{"text": "File Edit View", "x": 100, "y": 15}
]
}
# Full screen OCR
curl -X POST http://127.0.0.1:8745/api/ocr -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{}'
# Region-only (faster, ~10x for small regions)
curl -X POST http://127.0.0.1:8745/api/ocr -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"region":[0,0,800,100]}'
Acceso de Visión (Modelos de Imagen)
Tres endpoints para modelos que pueden consumir imágenes:
| Endpoint | Descripción |
|---|---|
GET /api/vision/frame | Fotograma JPEG individual (crudo o base64) |
GET /api/stream | Transmisión en vivo MJPEG |
POST /api/vision/diff | Detección de movimiento basada en texto (sin necesidad de visión) |
GET /api/vision/frame
| Parámetro | Predeterminado | Descripción |
|---|---|---|
scale | 1.0 | Factor de reducción (0.5 = mitad de tamaño) |
gray | 0 | 1 para escala de grises |
quality | 80 | Calidad JPEG (20-95) |
format | — | base64 para respuesta JSON |
region | — | x,y,w,h subregión |
# Half-size greyscale frame as base64 (for text-only models)
curl "http://127.0.0.1:8745/api/vision/frame?scale=0.5&gray=1&format=base64" \
-H "X-Auth-Token: $TOKEN"
GET /api/stream
Transmisión en vivo MJPEG. Colócala en <img src> o consúmela cuadro por cuadro.
| Parámetro | Predeterminado | Descripción |
|---|---|---|
fps | 10 | Fotogramas por segundo (1-30) |
quality | 70 | Calidad JPEG |
scale | 1.0 | Factor de reducción |
region | — | x,y,w,h subregión |
POST /api/vision/diff
Detección de movimiento basada en texto — no requiere modelo de visión.
| Cuerpo | Descripción |
|---|---|
{} | Comparar con el último fotograma almacenado |
{"grab":"gray"} | Almacenar el fotograma actual para la siguiente comparación |
{"b64_prev":"..."} | Comparar con el fotograma anterior proporcionado |
{
"ok": true,
"changed": true,
"changed_pct": 12.5,
"bbox": [100, 200, 400, 350],
"tiles": [
{"row": 2, "col": 4, "pct": 35.2, "center": [1000, 390]}
]
}
Gestión de Ventanas
GET /api/windows
Lista todas las ventanas visibles.
{
"ok": true,
"windows": [
{
"hwnd": 123456,
"title": "My Application",
"process": "app.exe",
"pid": 7890,
"focused": true,
"rect": [0, 0, 1920, 1080],
"desktop": 1
}
]
}
POST /api/window
| acción | Requerido | Opcional | Descripción |
|---|---|---|---|
focus | hwnd | — | Traer ventana al frente |
close | hwnd | expect_title, expect_process | Cierre seguro mediante WM_CLOSE |
kill | hwnd, pid | — | Forzar terminación (estilo administrador de tareas) |
topmost | hwnd | — | Establecer siempre al frente |
untopmost | hwnd | — | Quitar siempre al frente |
maximize | hwnd | — | Maximizar ventana |
# Focus a window
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"hwnd":12345,"action":"focus"}'
# Safe close (with title verification)
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"close","hwnd":12345,"expect_title":"Notepad"}'
# Kill process
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"kill","hwnd":12345,"pid":7890}'
Control Sin Enfoque (En Segundo Plano)
Lee y controla ventanas sin robar el enfoque — el usuario sigue trabajando en su escritorio principal.
GET /api/window/capture
Captura una ventana mediante PrintWindow (funciona incluso en otro escritorio virtual).
| Parámetro | Descripción |
|---|---|
hwnd (requerido) | Identificador de ventana |
client | 1 = solo área de cliente |
ocr | 1 = devolver texto OCR en lugar de imagen |
# Capture window as PNG
curl "http://127.0.0.1:8745/api/window/capture?hwnd=12345" \
-H "X-Auth-Token: $TOKEN" -o window.png
# Capture + OCR in one call
curl "http://127.0.0.1:8745/api/window/capture?hwnd=12345&ocr=1" \
-H "X-Auth-Token: $TOKEN"
POST /api/window/post
Envía entrada a una ventana, eligiendo automáticamente la ruta de entrega.
| acción | Descripción |
|---|---|
type | Escribir texto (compatible con Unicode) |
key | Enviar una pulsación de tecla |
hotkey | Enviar una combinación de teclas |
click | Hacer clic en coordenadas de cliente |
scroll | Desplazar la ventana |
drag | Arrastrar dentro de la ventana |
El parámetro opcional mode controla el enrutamiento:
| modo | Comportamiento |
|---|---|
auto (predeterminado) | Decidido por la sonda de modo de entrada (ver más abajo) |
background | Forzar ruta PostMessage (la ventana conserva enfoque/orden z) |
focused | Forzar ruta de enfoque + SendInput |
# Type into a background Notepad
curl -X POST http://127.0.0.1:8745/api/window/post -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hwnd":12345,"action":"type","text":"Hello from background!"}'
Reglas de enrutamiento (mode=auto):
postmessage— aplicación Win32 clásica: PostMessage en segundo plano, sin cambio de enfoque.uia— superficie WinUI/UWP/XAML (lienzo DirectX único, sin controles secundarios Win32): los mensajes publicados se ignoran silenciosamente, por lo que la ventana se enfoca y la acción se reproduce mediante SendInput (las coordenadas de cliente se convierten a pantalla). Este es el respaldo documentado para aplicaciones modernas.focused— la ventana ya está en primer plano: ruta SendInput enfocada.invalid— HTTP 409; no es una ventana de nivel superior accesible.
GET /api/window/input-mode
Clasifica cómo recibe entrada una ventana antes de publicar en ella. Devuelve uno de
focused | postmessage | uia | invalid.
curl "http://127.0.0.1:8745/api/window/input-mode?hwnd=12345" \
-H "X-Auth-Token: $TOKEN"
Nota WinUI: El Bloc de notas nuevo (y otras aplicaciones alojadas en XAML) no tiene control Edit secundario clásico al que publicar — toda la interfaz es una superficie DirectX.
input-modeinformauiapara estas;/api/window/postentonces usa automáticamente la ruta SendInput enfocada./api/window/childrensigue siendo útil para aplicaciones clásicas con controles secundarios reales.
GET /api/window/children
Lista los controles secundarios de una ventana (nombre de clase + título + hwnd).
curl "http://127.0.0.1:8745/api/window/children?hwnd=12345" -H "X-Auth-Token: $TOKEN"
Escritorios Virtuales
GET /api/desktops
Lista todos los escritorios virtuales.
POST /api/desktop
| acción | Parámetros | Descripción |
|---|---|---|
switch | number | Cambiar al escritorio N |
create | — | Crear un nuevo escritorio |
curl http://127.0.0.1:8745/api/desktops -H "X-Auth-Token: $TOKEN"
curl -X POST http://127.0.0.1:8745/api/desktop -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"switch","number":2}'
Modo Juego
| acción | Parámetros | Descripción |
|---|---|---|
start | sensitivity (predeterminado 12) | Bloquear cursor al centro, habilitar entrada de juego |
move | dx, dy, sensitivity | Rotar cámara (ratón relativo) |
stop | — | Liberar cursor + toda entrada mantenida |
heartbeat | — | Mantener vivo para pulsaciones largas |
# Start game mode
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"start","sensitivity":12}'
# Look right
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"move","dx":50,"dy":0}'
# Hold W to walk forward
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","key":"w"}'
# ... later ...
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"up","key":"w"}'
# Stop game mode
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"stop"}'
Puntos Finales de Seguridad
GET /api/held
Devuelve las teclas/botones actualmente mantenidos y el estado del vigilante.
{
"ok": true,
"keys": ["w", "shift"],
"buttons": ["left"],
"game_mode": true,
"idle_seconds": 5.2,
"watchdog_count": 0,
"last_watchdog": null
}
POST /api/release_all
Emergencia: liberar todo (teclas mantenidas, botones del ratón, bloqueo de cursor en modo juego).
curl -X POST http://127.0.0.1:8745/api/release_all -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{}'
🤖 Para Agentes de IA
Una guía dedicada e integral para agentes de IA (LLMs, modelos de visión, marcos de automatización) está disponible en AGENT_GUIDE.md.
Cubre:
- Bucle percibir-actuar (leer → planificar → actuar → verificar)
- Guardia de enfoque (
expect_hwnd) para prevenir accidentes de ventana incorrecta - Flujos de trabajo de automatización de aplicaciones y control de juegos
- Acceso de visión para modelos capaces de imágenes
- Detección de movimiento basada en texto
- Optimización de ancho de banda
- Ejemplos completos de curl
Soporte MCP (Agentes en la Nube con Un Clic)
Protocolo de Contexto de Modelo (MCP) convierte este proyecto en una caja de herramientas plug-and-play para cualquier agente compatible con MCP: Claude Desktop, Claude Code, Cursor, modo Agente de VS Code Copilot, agentes en la nube personalizados — sin código de conexión personalizado, sin scripts curl. El agente descubre y llama las herramientas de forma nativa.
Cómo funciona
MCP agent (cloud or desktop)
│ MCP protocol (stdio or streamable-HTTP)
▼
mcp_server.py ← thin wrapper: tools → HTTP calls, token auto-read
│ REST + X-Auth-Token (localhost only)
▼
server.py ← the single source of truth:
auth, locks, watchdog, focus guard, all safety rules
mcp_server.py agrega sin poderes nuevos — cada mecanismo de seguridad
(token de autenticación, bloqueos de entrada/lectura, vigilante, bloqueo Alt+F4, guardia
de enfoque, seguro contra fallos) permanece aplicado por server.py.
Configuración
pip install mcp # optional dependency (see requirements.txt)
python server.py # start the REST server first (it writes .token)
El servidor MCP lee automáticamente el token de .token (o la
variable de entorno SCREEN_CONTROL_TOKEN) — configuración cero.
Agentes de escritorio (transporte stdio)
Claude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"screen-control": {
"command": "python",
"args": ["C:/path/to/screen-control/mcp_server.py"]
}
}
}
Claude Code: claude mcp add screen-control -- python C:/path/to/screen-control/mcp_server.py
Cursor / VS Code: agrega la misma entrada a sus archivos de configuración MCP.
Agentes remotos / en la nube (transporte streamable-HTTP)
python mcp_server.py --http --port 8751
# MCP endpoint: http://127.0.0.1:8751/mcp
El transporte HTTP está protegido por token: cada solicitud debe llevar el
encabezado X-Auth-Token (mismo token que el servidor REST). Los tokens en la
cadena de consulta (?token=...) son rechazados por diseño — las URL se
filtran en registros de proxy/túnel, historial del navegador y enlaces compartidos, y
este token otorga control total del escritorio. Los clientes que no pueden enviar
encabezados personalizados deben ejecutar un mcp_server.py stdio local en su
lugar. Solo GET /health está abierto, para sondas de actividad.
La protección contra reenlace de DNS está deshabilitada deliberadamente en este
transporte — las solicitudes tunelizadas llegan con un encabezado Host
extranjero, y la amenaza de reenlace ya está cubierta por la protección del token.
Para un agente en la nube, exponlo a través de un túnel:
cloudflared tunnel --url http://127.0.0.1:8751
# → prints a https://<random>.trycloudflare.com URL
Luego configura la conexión MCP del agente con <tunnel-url>/mcp más el
token de .token como encabezado (X-Auth-Token).
Conectores sin encabezados (claves con alcance)
Para clientes que no pueden enviar encabezados personalizados (por ejemplo, conectores web que solo aceptan una URL de punto final), crea una clave API con alcance — una credencial persistente, opcionalmente limitada en el tiempo — e incrústala en la ruta de la URL:
# Create a 24-hour scoped key (requires the server to be running)
curl -X POST http://127.0.0.1:8745/api/keys -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"create","name":"spark","expires_in_hours":24}'
# Connector endpoint becomes:
# https://<tunnel-url>/mcp/<scoped-key>
Garantías de diseño (SC-06):
- El token de sesión maestro se rechaza en las URL (403) — solo las claves con alcance pueden viajar allí
- Las claves con alcance expiran automáticamente; las claves expiradas no autentican nada
- Las claves con alcance son revocables por nombre en cualquier momento mediante
POST /api/keys({"action":"revoke","name":"spark"}) — la revocación tiene efecto inmediatamente en cada punto final - El archivo
.apikeysalmacena solo hashes SHA-256, nunca claves en bruto
⚠️ Un túnel expone el control de la PC a internet. Mantén el token en secreto, prefiere túneles de corta duración y claves con alcance para conectores sin encabezados, y detén el servidor cuando no esté en uso.
Inicio con Un Comando (lanzador + túnel automático)
start-server.bat automatiza toda la configuración en la nube e imprime todo
lo que tu agente en la nube necesita, listo para pegar:
- Descarga
cloudflared.exesi falta (portátil, sin necesidad de administrador) - Detiene instancias sobrantes de una ejecución anterior
- Inicia el servidor REST (puerto 8745) y el servidor HTTP MCP (puerto 8751)
- Espera hasta que ambos estén saludables (sondas
/tokeny/health) - Inicia un túnel rápido cloudflared, extrae su URL pública de
tunnel.log, e imprime el resumen:
============================================================
ALL SYSTEMS RUNNING
============================================================
Local REST API : http://127.0.0.1:8745
Local MCP : http://127.0.0.1:8751/mcp
Public MCP URL : https://<random>.trycloudflare.com/mcp
------------------------------------------------------------
PASTE INTO YOUR CLOUD AGENT (MCP connector settings)
------------------------------------------------------------
Endpoint : https://<random>.trycloudflare.com/mcp
Header : X-Auth-Token: <token>
URL form : https://<random>.trycloudflare.com/mcp?token=<token>
(only if the connector cannot send headers)
------------------------------------------------------------
stop-server.bat detiene los tres (REST, MCP, túnel) de una sola vez.
Herramientas disponibles (16)
| Categoría | Herramientas |
|---|---|
| Percepción | get_info, ocr_screen, screenshot (bloque de imagen real para modelos de visión), motion_diff |
| Ratón / teclado | mouse, keyboard (con expect_hwnd), get_held, release_all |
| Ventanas | list_windows, focus_window, window_children, window_input_mode, window_post, window_capture_ocr, close_window |
| Modo juego | game (iniciar / mover / detener / latido) |
Qué transporte para quién
| Consumidor | Transporte | Comando |
|---|---|---|
| Claude Desktop / Cursor / VS Code (local) | stdio | python mcp_server.py |
| Claude Code | stdio | claude mcp add ... (arriba) |
| Agentes en la nube / remotos | streamable-HTTP | start-server.bat (recomendado) o python mcp_server.py --http --port 8751 + cloudflared tunnel --url http://127.0.0.1:8751 |
Nota: Este proyecto apunta al SDK de Python MCP 2.x (API
MCPServer). Con SDK 1.x, reemplaza la importación confrom mcp.server.fastmcp import FastMCP, ImageyMCPServerconFastMCP.
Modelo de Seguridad
Amenaza: Páginas Web Maliciosas (CSRF)
Incluso vinculado a 127.0.0.1, una página maliciosa en el navegador puede
desencadenar solicitudes sin verificación previa (fetch text/plain, POST de formulario
HTML) a localhost. El navegador bloquea la respuesta pero no la solicitud —
el servidor aún ejecutaría el comando.
Mitigación: Cada solicitud requiere X-Auth-Token. Una página extranjera
no puede leer este token (Política de Mismo Origen), por lo que no puede autenticarse.
Capas adicionales:
- Las solicitudes POST deben usar
Content-Type: application/json(415 de lo contrario) - Esto bloquea POSTs codificados en formularios y text-plain incluso si el token se filtra
- Confianza en el encabezado Host (reenlace de DNS): cuando está vinculado a
loopback, las solicitudes que llevan un encabezado
Hostque no es loopback se rechazan con421— una página de reenlace que resuelve su dominio a127.0.0.1no puede leer/tokenni llamar a la API - Las respuestas
/tokeny/llevanCache-Control: no-storepara que la credencial nunca sea persistida por navegadores o proxies
Amenaza: Teclas Atascadas / Bloqueo de Modo Juego
En modo juego, ClipCursor fija el cursor a una caja de 2×2 — el seguro
contra fallos clásico de pyautogui (cursor a la esquina superior izquierda) no funciona.
Mitigaciones:
Esc/Alt+Tabfísico — entrada de hardware real; esta API no puede bloquearlo, y siempre funcionaPOST /api/release_all— liberación instantánea de todo- Vigilante (automático) — 30 s de inactividad del lado del servidor con entrada mantenida desencadena liberación automática
Amenaza: Escritura en Ventana Incorrecta
Mitigaciones:
- Guardia
expect_hwnden/api/key— si la ventana en primer plano no coincide, la escritura se rechaza con 409 - La ruta enfocada de
/api/window/postverifica el enfoque después del cambio de enfoque y antes de cualquier entrada sintética (409 en desajuste) — la entrada nunca se reproduce en la ventana que esté en primer plano focus_window()genera error en fallo en lugar de devolver silenciosamente
Amenaza: Combinaciones de Teclas Peligrosas
Mitigación: Bloqueado a nivel de API (403) en cada ruta de entrega —
la ruta directa /api/key, la ruta en segundo plano /api/window/post
(PostMessage) y la ruta de respaldo enfocada comparten una única política de
seguridad (control._assert_allowed):
Alt+F4— la única combinación Alt bloqueada (Alt+Tab, Alt+menú son legítimas)- Tecla Win — evita el menú Inicio, el cambio de tareas
Ctrl+Alt+Del— pantalla de seguridad del sistema- Estilo
Shift+Delete— evita la eliminación permanente
Amenaza: Matar procesos del sistema
Mitigaciones:
- El nombre del proceso se resuelve directamente desde el PID (instantánea toolhelp de Win32), no desde el inventario de ventanas visibles — los procesos del sistema en segundo plano/sin ventana reciben la misma protección que los visibles
- Los procesos críticos del sistema están en la lista negra (denegación predeterminada
para PIDs desconocidos):
winlogon.exe,csrss.exe,smss.exe,services.exe,lsass.exe,svchost.exe,system,registry,dwm.exe - Confirmación opcional de
expect_process: una discrepancia aborta la eliminación con409— protege contra matar un PID reutilizado recientemente
Amenaza: Agotamiento de recursos (agente malicioso / DoS)
Un cliente que posee un token pero se comporta mal no debería poder agotar la memoria o bloquear el bloqueo de entrada.
Mitigaciones:
MAX_CONTENT_LENGTH= 1 MB — los cuerpos de solicitud sobredimensionados se rechazan (413)- El ancho/alto/área de
regionyscaleestán limitados (400 en caso contrario) - Los payloads de
textestán limitados a 10,000 caracteres por llamada de entrada - Los flujos MJPEG están limitados a 10 clientes concurrentes (429 más allá de eso)
- El límite de bomba de descompresión de Pillow está configurado para imágenes proporcionadas por el cliente
Acceso a la red
El servidor se vincula a 127.0.0.1 por defecto. Para exponerlo a la red:
python server.py --host 0.0.0.0 # ⚠️ anyone on the network can control this machine
Guía del modo de juego
Configuración
# 1. Focus the game window
curl -X POST http://127.0.0.1:8745/api/window -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"hwnd":GAME_HWND,"action":"focus"}'
# 2. Start game mode
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"start","sensitivity":12}'
Mirada de cámara
# Look right
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"move","dx":50,"dy":0}'
# Look down
curl -X POST http://127.0.0.1:8745/api/game -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"move","dx":0,"dy":30}'
Movimiento
# Walk forward (hold W)
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","key":"w"}'
# ... walk for a while ...
# Release W
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"up","key":"w"}'
Específico de Minecraft
# Place block (right-click)
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"click","button":"right","x":960,"y":540}'
# Break block (hold left-click)
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"down","button":"left"}'
# ... after breaking ...
curl -X POST http://127.0.0.1:8745/api/mouse -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"up","button":"left"}'
# Select hotbar slot
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"press","key":"1"}'
# Open inventory
curl -X POST http://127.0.0.1:8745/api/key -H "X-Auth-Token: $TOKEN" \
-H "Content-Type: application/json" -d '{"action":"press","key":"e"}'
Idoneidad
| Tipo de juego | ¿Adecuado? | Notas |
|---|---|---|
| Minecraft (construcción) | ✅ Sí | Colocar bloques, caminar, minar |
| Minecraft (PvP) | ❌ No | Demasiado lento para combate rápido |
| Juegos por turnos | ✅ Sí | Tiempo amplio para leer→actuar→verificar |
| RPG / aventura | ✅ Sí | Inventario, diálogo, exploración |
| FPS rápido | ❌ No | Tiempo de reacción insuficiente |
| Juegos de puzzle | ✅ Sí | Basado en clics, con mucha lectura |
Guía de acceso a la visión
Para modelos con capacidad de imagen
Si el modelo consumidor puede procesar imágenes, use los endpoints de visión directamente:
GET /api/vision/frame?scale=0.5&gray=1&quality=70
Esto devuelve un único JPEG que el modelo puede analizar para:
- Elementos del HUD del juego (salud, maná, inventario)
- Texto en pantalla (menús, chat, información sobre herramientas)
- Comprensión visual de la escena (bloques, entidades, terreno)
Para modelos solo de texto
Use el endpoint de diferencias para la detección de movimiento sin visión:
POST /api/vision/diff {"grab":"gray"} → first call: stores frame
POST /api/vision/diff → subsequent calls: returns diff
La respuesta le indica dónde cambiaron las cosas (coordenadas de mosaico) y cuánto (porcentaje), lo cual es suficiente para:
- Detectar que una acción tuvo un efecto
- Localizar elementos en movimiento en la pantalla
- Rastrear cambios de estado de animación
Optimización del ancho de banda
| Enfoque | Payload | Caso de uso |
|---|---|---|
scale=1.0, gray=0 | ~500 KB | Detalle completo |
scale=0.5, gray=1 | ~50 KB | Bueno para la mayoría de los modelos de visión |
scale=0.25, gray=1 | ~10 KB | Compresión máxima |
diff (texto) | ~1 KB | Agentes solo de texto |
region=... | Variable | Enfocarse en un área específica |
Solución de problemas
"Motor OCR no instalado"
pip install rapidocr-onnxruntime
El servidor no se inicia (puerto en uso)
# Find the process using port 8745
netstat -ano | findstr ":8745"
# Kill it
taskkill /PID <pid> /F
"Discrepancia de enfoque" (409) al escribir
La ventana en primer plano cambió entre la llamada de enfoque y la llamada de
escritura. Solución: siempre pase expect_hwnd y verifique el enfoque antes de escribir.
Ventana no encontrada
La ventana puede haberse cerrado o puede ser una ventana del sistema que
EnumWindows no expone. Intente:
curl http://127.0.0.1:8745/api/windows -H "X-Auth-Token: $TOKEN"
Cursor del modo de juego atascado
Use POST /api/release_all o presione Esc / Alt+Tab físicamente.
Alta latencia de OCR
El OCR en una pantalla completa de 1920×1080 puede tardar desde unos segundos hasta ~30 s dependiendo de su CPU y la complejidad en pantalla. Use una región — los recortes pequeños suelen ser 10 veces más rápidos:
{"region": [0, 0, 800, 100]}
Los caracteres turcos no aparecen
El sistema usa SendInput + KEYEVENTF_UNICODE que es independiente de la distribución.
Si los caracteres aún no aparecen, la aplicación de destino puede no admitir
entrada Unicode — intente POST /api/window/post con action: "type" en su lugar.
Estructura del proyecto
screen-control/
├── server.py # Flask HTTP server + all API endpoints
├── core/ # PlatformBackend interface + standardized errors
│ ├── backends.py # Abstract backend + lazy discovery
│ └── errors.py # ApiError envelope + error codes
├── backends/ # OS implementations behind PlatformBackend
│ ├── windows.py # Reference backend (moved from control.py)
│ ├── linux.py # Fail-closed stub (ROADMAP Phase 5)
│ ├── macos.py # Fail-closed stub (ROADMAP Phase 7)
│ ├── fake.py # In-memory backend for tests
│ └── forbidden.py # Shared blocked-key policy
├── control.py # Compatibility shim re-exporting backends.windows
├── tests/unit/ # Offline unit + integration tests (no real input)
├── mcp_server.py # MCP server (stdio + streamable-HTTP) — thin wrapper over the API
├── sdk/
│ └── screen_control.py # Python SDK client (pip-installable style)
├── index.html # Bundled web UI (live view + control panels)
├── docs/images/ # README assets (demo GIF captured by the API itself)
├── requirements.txt # Python dependencies
├── start-server.bat # One command: REST + MCP + cloud tunnel (Windows)
├── stop-server.bat # Stop all three processes
├── test-security.py # Security + game-mode test suite (34 checks)
├── test-game.py # Live game-mechanics test (app launch → draw → safe close)
├── test-endtoend.py # End-to-end test: open Notepad → type → save → verify
├── .github/workflows/ # CI: runs the security suite on every push
├── AGENT_GUIDE.md # AI agent integration guide (separate from this file)
├── README.md # This file
└── .token # Auto-generated auth token (gitignored)
Pruebas
Requisitos previos
El servidor debe estar en ejecución:
cd screen-control
python server.py
Suite de pruebas de seguridad
Prueba autenticación, combinaciones de teclas bloqueadas, gestión de ventanas, cierre seguro, protección de procesos críticos y modo de juego — todo no destructivo.
cd screen-control
python test-security.py
Salida esperada:
== Token Authentication ==
✓ Missing token -> 401
✓ Wrong token -> 401
✓ Correct token -> 200
✓ Non-JSON POST -> 415
== Blocked Key Combos ==
✓ Alt+F4 blocked (403)
✓ Win key blocked (403)
✓ Win+D blocked (403)
✓ Delete blocked (403)
== Window List ==
✓ Windows list requires GET
✓ Window list is non-empty — 8 windows
✓ Exactly one focused window
== Safe Close Verification ==
✓ Wrong title aborts close
== Critical Process Protection ==
✓ System process (pid 4) rejected (403)
✓ pid 0 rejected (403)
== Game Mode ==
✓ Game mode started
✓ Relative camera look
✓ Game mode stopped
== Watchdog (dry run) ==
✓ Held state returns ok
✓ Watchdog count reported
========================================
RESULT: 19 passed, 0 failed
Prueba en vivo de mecánicas de juego
Lanza una aplicación real (mspaint o notepad), realiza mecánicas de juego de mantener presionado para dibujar, verifica mediante análisis de píxeles y luego cierra de forma segura con el manejo del diálogo "No guardar".
cd screen-control
python test-game.py
Nota: Esta prueba lanza una aplicación real. Maneja la limpieza automáticamente (envía WM_CLOSE y hace clic en "No guardar" si aparece un diálogo).
Contribuciones
- Haga un fork del repositorio
- Cree una rama de características
- Realice sus cambios
- Pruebe en una máquina con Windows
- Envíe una solicitud de extracción
Estilo de código
- Python: PEP 8, anotaciones de tipo, docstrings en todas las funciones públicas
- Docstrings: Inglés, estilo Google
- Mensajes de error: Inglés, descriptivos
- Comentarios: Inglés, explique por qué no qué
Licencia
Licencia MIT. Consulte LICENSE para más detalles.
Construido con ❤️ para automatización local e investigación de agentes de IA.