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

screen-control logo

License: MIT Platform Python CI: security tests MCP Listed on mcpservers.org

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.

An AI agent typing into Notepad through the screen-control API

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


Características

CaracterísticaDescripción
🖼️ Transmisión de pantalla en vivoCaptura de pantalla que se actualiza continuamente en el navegador
🖱️ Control de mouseClic, clic derecho, doble clic, desplazamiento, arrastrar y soltar mediante captura en vivo
⌨️ Control de tecladoEscritura de texto (incluye Unicode/Turco, independiente del diseño), teclas y atajos (Ctrl+C, Alt+Tab…)
👁️ OCRConvierte el texto en pantalla a formato legible por máquina
📷 Acceso de visiónRutas 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 ventanasListar, enfocar, cerrar de forma segura (WM_CLOSE), matar (estilo administrador de tareas)
🖥️ Control sin focoLeer/escribir ventanas en segundo plano mediante PostMessage sin robar el foco
🎮 Modo juegoMirada de cámara mediante movimiento relativo del mouse, teclas de mantener presionado para moverse
🔐 Autenticación por tokenCada solicitud requiere X-Auth-Token (protección CSRF)
🦺 Vigilante de entrada atascadaLibera automáticamente las teclas mantenidas después de 30 s de inactividad
🛟 Mecanismo de seguridadEl 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:

BloqueoProtegeEndpoints
_input_lockmouse, teclado, modo juego, operaciones de ventana/api/mouse, /api/key, /api/game, /api/window/post, ...
_read_lockcaptura, 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:

  1. LEER — OCR o visión lee la pantalla antes y después de cada acción
  2. UNA ACCIÓN — cada ronda envía un solo comando
  3. VERIFICAR — el criterio de aceptación es "apareció en pantalla", no "lo envié"
  4. 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

PaquetePropósito¿Requerido?
mssCaptura de pantalla rápida✅ Sí
pyautoguiControl de mouse/teclado✅ Sí
pyvdaGestión de escritorios virtuales✅ Sí
flaskServidor HTTP✅ Sí
PillowProcesamiento de imágenes✅ Sí
rapidocr-onnxruntimeOCR (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

CapacidadWindowsLinux X11Linux WaylandmacOS
Captura de pantallaCompletaCompletaDependiente del portalPermiso requerido
OCRCompleta/opcionalCompleta/opcionalCompleta/opcionalCompleta/opcional
Control de mouseCompletoCompletoRestringidoPermiso de accesibilidad
Control de tecladoCompletoCompletoRestringidoPermiso de accesibilidad
Enumeración de ventanasCompletaDependiente del WMLimitadaDependiente de accesibilidad/API
Entrada en segundo planoFuerteDependiente del WM/aplicaciónGeneralmente no disponibleLimitada
Escritorios virtualesCompatibleDependiente del DE/WMDependiente del DE/WMEspecífico de Spaces
Modo juegoCompatibleExperimentalLimitadoExperimental

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ódigoSignificado
401Token faltante o inválido
415POST sin Content-Type: application/json

Arranque del token (para la interfaz web incluida):

GET /token
→ {"ok": true, "token": "abc123..."}

El endpoint /token es 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ámetroTipoPredeterminadoDescripción
monitorint1Índice del monitor
regionstring—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ónParámetros requeridosParámetros opcionalesDescripción
movex, yduration (predeterminado 0.15)Mover el cursor a una posición absoluta
clickx, ybutton (izquierdo/derecho), clicks (predeterminado 1)Hacer clic en una posición
scrollclicksx, yRueda de desplazamiento (positivo=arriba)
dragx1, y1, x, yduration, buttonArrastrar entre dos puntos
downbutton (predeterminado "izquierdo")—Mantener presionado el botón del mouse
upbutton (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ónParámetros requeridosDescripción
presskeyPresionar y soltar una tecla
downkeyMantener una tecla presionada (registrada para el vigilante)
upkeySoltar una tecla mantenida
hotkeykeys (matriz)Combinación de teclas (p. ej., ["ctrl","c"])
typetextEscribir texto (Unicode, independiente del diseño)
Parámetro opcionalPredeterminadoDescripción
expect_hwnd—Identificador de ventana para verificar el foco (409 si no coincide)
interval0.03Retraso 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ámetroTipoPredeterminadoDescripción
regionmatriz—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:

EndpointDescripción
GET /api/vision/frameFotograma JPEG individual (crudo o base64)
GET /api/streamTransmisión en vivo MJPEG
POST /api/vision/diffDetección de movimiento basada en texto (sin necesidad de visión)

GET /api/vision/frame

ParámetroPredeterminadoDescripción
scale1.0Factor de reducción (0.5 = mitad de tamaño)
gray01 para escala de grises
quality80Calidad 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ámetroPredeterminadoDescripción
fps10Fotogramas por segundo (1-30)
quality70Calidad JPEG
scale1.0Factor 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.

CuerpoDescripció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ónRequeridoOpcionalDescripción
focushwnd—Traer ventana al frente
closehwndexpect_title, expect_processCierre seguro mediante WM_CLOSE
killhwnd, pid—Forzar terminación (estilo administrador de tareas)
topmosthwnd—Establecer siempre al frente
untopmosthwnd—Quitar siempre al frente
maximizehwnd—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ámetroDescripción
hwnd (requerido)Identificador de ventana
client1 = solo área de cliente
ocr1 = 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ónDescripción
typeEscribir texto (compatible con Unicode)
keyEnviar una pulsación de tecla
hotkeyEnviar una combinación de teclas
clickHacer clic en coordenadas de cliente
scrollDesplazar la ventana
dragArrastrar dentro de la ventana

El parámetro opcional mode controla el enrutamiento:

modoComportamiento
auto (predeterminado)Decidido por la sonda de modo de entrada (ver más abajo)
backgroundForzar ruta PostMessage (la ventana conserva enfoque/orden z)
focusedForzar 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-mode informa uia para estas; /api/window/post entonces usa automáticamente la ruta SendInput enfocada. /api/window/children sigue 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ónParámetrosDescripción
switchnumberCambiar 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ónParámetrosDescripción
startsensitivity (predeterminado 12)Bloquear cursor al centro, habilitar entrada de juego
movedx, dy, sensitivityRotar 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 .apikeys almacena 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:

  1. Descarga cloudflared.exe si falta (portátil, sin necesidad de administrador)
  2. Detiene instancias sobrantes de una ejecución anterior
  3. Inicia el servidor REST (puerto 8745) y el servidor HTTP MCP (puerto 8751)
  4. Espera hasta que ambos estén saludables (sondas /token y /health)
  5. 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íaHerramientas
Percepciónget_info, ocr_screen, screenshot (bloque de imagen real para modelos de visión), motion_diff
Ratón / tecladomouse, keyboard (con expect_hwnd), get_held, release_all
Ventanaslist_windows, focus_window, window_children, window_input_mode, window_post, window_capture_ocr, close_window
Modo juegogame (iniciar / mover / detener / latido)

Qué transporte para quién

ConsumidorTransporteComando
Claude Desktop / Cursor / VS Code (local)stdiopython mcp_server.py
Claude Codestdioclaude mcp add ... (arriba)
Agentes en la nube / remotosstreamable-HTTPstart-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 con from mcp.server.fastmcp import FastMCP, Image y MCPServer con FastMCP.


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 Host que no es loopback se rechazan con 421 — una página de reenlace que resuelve su dominio a 127.0.0.1 no puede leer /token ni llamar a la API
  • Las respuestas /token y / llevan Cache-Control: no-store para 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:

  1. Esc / Alt+Tab físico — entrada de hardware real; esta API no puede bloquearlo, y siempre funciona
  2. POST /api/release_all — liberación instantánea de todo
  3. 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_hwnd en /api/key — si la ventana en primer plano no coincide, la escritura se rechaza con 409
  • La ruta enfocada de /api/window/post verifica 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 con 409 — 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 region y scale están limitados (400 en caso contrario)
  • Los payloads de text está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)❌ NoDemasiado 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❌ NoTiempo 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

EnfoquePayloadCaso de uso
scale=1.0, gray=0~500 KBDetalle completo
scale=0.5, gray=1~50 KBBueno para la mayoría de los modelos de visión
scale=0.25, gray=1~10 KBCompresión máxima
diff (texto)~1 KBAgentes solo de texto
region=...VariableEnfocarse 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

  1. Haga un fork del repositorio
  2. Cree una rama de características
  3. Realice sus cambios
  4. Pruebe en una máquina con Windows
  5. 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.