Skip to main content
Glama
casualkre

VoltageInputMcp

by casualkre

VoltageInputMcp

Un servidor MCP que permite a un modelo de frontera manejar un ordenador a velocidad de entrada en lugar de a velocidad de llamada de herramienta.

El problema

Las herramientas de uso de ordenador hacen un viaje de ida y vuelta a un modelo remoto para cada acción. Captura de pantalla arriba, decisión abajo, un clic. Eso está bien para rellenar un formulario y es inútil para cualquier cosa que necesite una secuencia de entradas entregadas rápidamente — jugar a un juego, trabajar con un diálogo modal, manejar una línea de tiempo, cualquier interfaz donde la tercera entrada dependa de que las dos primeras ya hayan llegado. El cuello de botella no es la inteligencia del modelo. Es que esa inteligencia está a 800 ms de distancia y las entradas necesitan estar a 8 ms de distancia.

La forma de la respuesta

Separar decidir de hacer, y poner el hacer en la misma máquina que el teclado.

  ┌─────────────────────────────────────────────────────────────────┐
  │  Layer 1  —  the orchestrator (Claude, or any MCP client)       │
  │  Writes a Playbook: states, what to look for, what is allowed,  │
  │  when to move on. Thinks once, up front. Watches and corrects.  │
  └───────────────────────────┬─────────────────────────────────────┘
                              │  MCP
  ┌───────────────────────────▼─────────────────────────────────────┐
  │  Layer 2  —  two small local models, on your GPU                │
  │                                                                 │
  │   vision (Qwen2.5-VL-3B)     "of these specific things,         │
  │                               which are on screen, and where?"  │
  │   actuator (Qwen3-1.7B)      "given that, which inputs?"        │
  │                                                                 │
  │  Neither plans. Both answer one closed question per cycle.      │
  └───────────────────────────┬─────────────────────────────────────┘
                              │
  ┌───────────────────────────▼─────────────────────────────────────┐
  │  safety governor  →  /dev/uinput  →  the actual desktop         │
  └─────────────────────────────────────────────────────────────────┘

El orquestador es el cerebro. Los modelos pequeños son los brazos. Los brazos no son inteligentes y nunca se les pide que lo sean.

De dónde viene realmente la velocidad

No de que los modelos pequeños sean rápidos — un VLM de 3B sigue costando ~300 ms. Viene de cuatro cosas, en orden descendente de impacto:

Ráfagas. El actuador no emite una entrada. Emite una ráfaga: un programa temporizado de entradas ejecutado por un ejecutor dedicado sin modelo en el bucle.

g:0;c:l;w:150;t:"README.md";k:enter;w:80;k:ctrl+s

Eso es una decisión y siete entradas que abarcan ~400 ms, programadas al milisegundo. Una ráfaga de 40 acciones sigue costando una decisión. La tasa de entrada la fija la ráfaga, no el modelo.

Reflejos. Reglas que se disparan con sondas de pantalla baratas — un píxel, un promedio de región — en microsegundos, entre decisiones, sin ningún modelo.

{"id": "heal", "when": "probe('health') < 0.25", "do": "k:q;w:60", "cooldown_ms": 800}

Omitir la percepción. La mayoría de los ciclos miran una pantalla que no ha cambiado. Una diferencia de fotograma de 40 µs decide si gastar 300 ms en el modelo de visión o reutilizar la última observación. En trabajo de escritorio ordinario esto omite el VLM en la mayoría de los ciclos.

Localidad de caché de prefijo. Los prompts se ordenan primero-estático para que llama.cpp reutilice la caché KV y solo re-procese la cola cambiada.

Por qué los modelos pequeños son fiables a pesar de ser pequeños

Porque no se les pide que sean fiables — están restringidos.

Bajo llama.cpp, ambos modelos generan contra una gramática GBNF que se regenera cada ciclo a partir del estado actual. La gramática no es un consejo. Enmascara los logits para que solo los tokens que continúan un análisis válido sean alcanzables. Concretamente, el actuador no puede:

  • emitir una ráfaga malformada

  • nombrar una tecla que la política deniegue — la tecla no está en la gramática

  • referenciar un elemento que no fue observado — el rango de índices se construye a partir del recuento de elementos de este ciclo

  • proponer una transición de estado que el Playbook no haya declarado

Y el modelo de visión no puede inventar un nombre de elemento de interfaz: su vocabulario de etiquetas es la lista watch que escribiste, más un pequeño conjunto genérico. Así que una guarda sees("address bar") compara contra un vocabulario cerrado en lugar de contra cualquier sustantivo que un modelo de 3B haya decidido producir.

No hay bucle de reintento ni análisis JSON defensivo, porque la salida malformada no es improbable — es irrepresentable.

El Playbook

No le das a los modelos pequeños un objetivo. Les das una máquina de estados. Las transiciones son expresiones de guarda evaluadas por el runtime, no por un modelo.

{
  "name": "open_downloads",
  "goal": "Open the file manager at ~/Downloads. Delete nothing, confirm nothing.",
  "initial": "launch",
  "policy": {
    "dry_run": true,
    "allow_verbs": ["g", "c", "k", "t", "w"],
    "deny_labels": ["delete", "trash", "confirm", "empty trash"]
  },
  "budget": { "max_cycles": 60, "max_seconds": 90 },
  "states": {
    "launch": {
      "brief": "Open the application launcher and start the file manager.",
      "watch": ["application launcher", "search field", "file manager icon"],
      "on_enter": "k:meta;w:400",
      "transitions": [
        { "when": "sees('search field')", "to": "type_name" },
        { "when": "cycles() > 6", "to": "@failure", "note": "launcher never opened" }
      ]
    },
    "navigate": {
      "brief": "Focus the location bar with ctrl+l, type the path, press Enter.",
      "watch": ["location bar", "file list", "error message"],
      "on_enter": "k:ctrl+l;w:200",
      "transitions": [
        { "when": "text('Downloads')", "to": "@success" },
        { "when": "sees('error message')", "to": "@failure" }
      ]
    }
  },
  "success_when": "text('Downloads') and not flag('loading')"
}

voltage_reference devuelve el DSL completo, el esquema JSON y la tabla de funciones de guarda, para que un orquestador pueda crear uno sin leer este repositorio.

Ajuste de rendimiento

Todos los números siguientes están medidos en la máquina de referencia (portátil RTX 3050 6 GB, Qwen2.5-VL-3B + Qwen3-1.7B bajo llama.cpp), no derivados.

Ambos modelos están limitados por decodificación. Los tokens de salida son la única palanca que importa.

Eso fue una sorpresa — el diseño originalmente asumía que la visión estaba limitada por prefill, y no lo está. El prefill midió ~28 ms y plano de 448×252 a 896×504. La decodificación corre a ~22 ms/token. Así que:

qué

coste

un token de salida

~22 ms

un elemento informado

~21 tokens ≈ 500 ms

visión, 2 elementos

~1.0 s

visión, 4 elementos

~2.2 s

actuador, prefijo en caché

140–400 ms según la longitud de la nota

Tres consecuencias, cada una de las cuales cambió un valor por defecto:

  • max_elements es el coste dominante de la visión. El valor por defecto es 3. Subirlo a 6 añade ~1.5 s por ciclo percibido. Ponlo al número que tus guardas realmente comprueban.

  • Reducir downscale_to no ayuda y normalmente perjudica. 448×252 midió 2.5× más lento que 896×504 — una imagen más borrosa hace que el modelo esté menos seguro, así que emite más tokens. Usa el tamaño más grande que quepa.

  • El campo note del actuador costaba el 55% de su latencia. Es puramente diagnóstico, y a 48 caracteres midió 412 ms/ciclo frente a 184 ms a 12 caracteres y 140 ms a 0. El valor por defecto ahora es 12.

Los elementos se codifican como [label_index, x1, y1, x2, y2] en lugar de {"l":"address bar","b":[...],"c":0.9} por la misma razón — medido 27–29% menos tokens y 32–41% menos latencia. Indexar en el vocabulario cerrado watch también es más seguro: el modelo no puede deletrear una etiqueta, y mucho menos escribirla mal.

La evaluación GBNF se ejecuta en la CPU una vez por token muestreado, así que el actuador recibe más hilos de CPU que el modelo de visión a pesar de estar completamente descargado a GPU — y restringir allow_keys es una optimización de latencia, no solo de seguridad.

Dos ajustes que fallan silenciosamente si están mal:

  • GGML_CUDA_FA_ALL_QUANTS=ON en tiempo de compilación. Servimos con caché KV q8_0 y atención flash. Sin esta bandera llama.cpp no compila kernels de FA para esa combinación de KV y cae a una ruta lenta — sin error, solo números misteriosamente malos. scripts/build-llama.sh la establece.

  • GGML_CUDA_ENABLE_UNIFIED_MEMORY=0 en tiempo de ejecución. Si es 1, el desbordamiento de VRAM se derrama silenciosamente por PCIe en lugar de fallar. Todo funciona y es ~10× más lento. serve.sh lo fija a off.

Mide en lugar de adivinar:

.venv/bin/voltage bench

Maneja ambos backends con las formas exactas de prompt que usa el bucle e informa de la latencia en frío vs. con prompt en caché, ms-por-token-visual en tres tamaños de entrada, y el tiempo de ciclo que implican. Una aceleración de caché de prompt por debajo de ~1.5× significa que algo dinámico se filtró al prefijo del prompt.

Comparando modelos

El experimento obvio — "qué modelo escribe mejores ráfagas" — mide lo incorrecto. La gramática ya garantiza que cada ráfaga es válida, así que un modelo más grande no puede ganar en sintaxis. Lo que realmente decide si una configuración es utilizable:

  1. Precisión de anclaje. Un modelo que es 200 ms más rápido y 40 px desviado es inútil — el clic falla. Medido como distancia del centro en píxeles de pantalla, no IoU, porque un clic aterriza en el centro.

  2. Calidad de decisión bajo restricción. Dada la misma observación, ¿elige la acción legal correcta, y encadena una secuencia completa en una ráfaga en lugar de emitir una acción tímida por ciclo?

  3. Latencia, que solo importa una vez que 1 y 2 son aceptables.

.venv/bin/voltage fixture desktop      # capture a real screen
.venv/bin/voltage compare              # score whatever is running now

La verdad de referencia proviene de capturas de pantalla reales etiquetadas por el modelo orquestador — que es la misma referencia que este sistema usa en tiempo de ejecución. La interfaz sintética es una trampa: un rectángulo dibujado no se lee como un botón a un modelo entrenado con interfaces reales, así que puntuar contra ella mide la habilidad equivocada.

Los resultados se acumulan entre ejecuciones, así que el flujo de trabajo es: servir perfil A → compare → servir perfil B → compare → leer la tabla. voltage compare --list la imprime sin volver a ejecutar.

Los fixtures son tuyos y no se commitean. Añade fixtures/ a .gitignore si tus capturas de pantalla contienen algo privado.

El bucle de aprendizaje

El primer playbook para un objetivo desconocido casi nunca es correcto. Lo que importa es que los fallos sean específicos, y que el siguiente intento comience desde lo que el último aprendió.

voltage_reference(section="loop")     the loop itself, and what each failure means
voltage_reference(section="bursts")   the burst cookbook: chaining, timing, game patterns

voltage_capture / voltage_observe     look before writing — check your labels exist
voltage_validate_playbook             dead guards, unreachable states, caught statically
voltage_run(dry_run=true)             real models, real screen, nothing injected
voltage_diagnose(run_id)              ← what to change, not raw data
voltage_learn(target=..., note=...)   record it; persists across sessions
voltage_lessons(target=...)           recall it before the next playbook

voltage_diagnose es la pieza que convierte esto en un bucle. Calcula lo que el diario implica pero no declara, y nombra la edición para cada cosa. En una ejecución atascada de Minecraft:

[BLOCKER] label_never_seen     never reported: ['crosshair', 'health bar']
[BLOCKER] input_not_landing    14 bursts executed, but the screen never changed
[BLOCKER] state_never_left     'mine' ran 14 cycles and never transitioned
[PROBLEM] timid_bursts         bursts averaged 1.0 actions
[HINT]    vision_every_cycle   vision ran on 100% of cycles

La distinción para la que existe: una ráfaga que nunca se ejecutó y una ráfaga que se ejecutó y no hizo nada se ven idénticas en un resumen y tienen causas no relacionadas. La primera es política o gramática. La segunda es foco de ventana, modo de puntero, o una aplicación que ignora la entrada sintética. Diagnose las separa comprobando si el fotograma realmente cambió después de la ejecución.

Aplica el hallazgo de mayor severidad, vuelve a ejecutar, diagnostica de nuevo. Un cambio a la vez — varios a la vez hacen que el siguiente diagnóstico sea ininterpretable.

Las lecciones persisten entre sesiones, con clave por objetivo, así que el segundo playbook para un juego comienza desde las coordenadas de sonda y los nombres de etiqueta funcionales que el primero descubrió:

voltage_learn(target="minecraft", kind="label",
              note="vision reports 'hotbar' reliably but never 'crosshair'")
voltage_learn(target="minecraft", kind="timing",
              note="block placement needs w:100 after right click or it does not register")

Seguridad

Lo que genera entradas es un modelo de 1.7B. El gobernador es la capa que no es consultiva: cada ráfaga pasa a través de él, incluidas las ráfagas de reflejo y las que escribiste tú mismo.

  • dry_run es el valor por defecto. Un Playbook nuevo analiza, comprueba y registra cada ráfaga sin tocar nada.

  • Rechazo de ráfaga completa. Ejecutar a medias una secuencia prevista es peor que no ejecutarla.

  • deny_labels rechaza un clic en cualquier cosa llamada Delete / Confirm / Purchase / Allow, dondequiera que aparezca — esto es lo que atrapa el diálogo que aparece en algún lugar inesperado.

  • Cercado de regiones, listas blancas de teclas, acordes denegados (ctrl+alt+delete, alt+f4), patrones de texto denegados (rm -rf, sudo), límites de tamaño de ráfaga y de entradas por segundo.

  • Cuatro paradas independientes: voltage stop (escribe un archivo — funciona por SSH), un temporizador de hombre muerto que se dispara en su propio hilo si el bucle se atasca, contención de entrada física (toca el ratón real y se detiene), y presupuestos del Playbook.

  • Las teclas mantenidas siempre se liberan — al abortar, al fallar, al expirar. Una ejecución interrumpida entre d:shift y u:shift no debe dejar Shift pulsado.

Instalación

De nada a funcionando, dos comandos.

Linux / macOS

git clone https://github.com/casualkre/voltage-input-mcp && cd voltage-input-mcp && ./install.sh

Windows (PowerShell)

git clone https://github.com/casualkre/voltage-input-mcp; cd voltage-input-mcp; powershell -ExecutionPolicy Bypass -File .\install.ps1

Después, en cualquiera de los dos:

voltage setup

install.sh se encarga de Python, los paquetes del sistema, el venv y tu PATH, e imprime las líneas sudo exactas para cualquier cosa que necesite root en lugar de pedirlo. voltage setup entonces detecta lo que ya tienes, descarga solo lo que falta, inicia los servidores de modelos, y se registra con tu cliente de IA — ejecutando cada paso, no describiéndolo. De diez a veinticinco minutos, casi todo tiempo de descarga. Seguro de re-ejecutar; retoma donde lo dejó.

Entonces solo ejecuta:

voltage

Setup detecta lo que ya tienes y continúa desde ahí. No asume un punto de partida: sondea tu SO, GPU, si llama.cpp u Ollama está instalado, qué modelos ya están descargados, si la entrada y la captura funcionan, y si el servidor MCP está registrado — entonces planifica solo los pasos que realmente quedan, y dice cuáles necesitan una decisión tuya y cuáles puede simplemente hacer. Si ya tienes Ollama, lo usa. Si no tienes ningún backend, explica el trade-off en dos líneas y te deja elegir.

Sin argumentos abre una consola interactiva: estado en vivo, setup guiado que arregla lo que no esté listo en orden de dependencias, un selector de modelos, un editor de configuración, registro con una tecla en Claude Code, y diagnósticos. Cada subcomando siguiente sigue funcionando de forma no interactiva, así que los scripts y CI no se ven afectados.

 ██╗   ██╗ ██████╗ ██╗  ████████╗ █████╗  ██████╗ ███████╗
 ██║   ██║██╔═══██╗██║  ╚══██╔══╝██╔══██╗██╔════╝ ██╔════╝
 ██║   ██║██║   ██║██║     ██║   ███████║██║  ███╗█████╗
 ╚██╗ ██╔╝██║   ██║██║     ██║   ██╔══██║██║   ██║██╔══╝
  ╚████╔╝ ╚██████╔╝███████╗██║   ██║  ██║╚██████╔╝███████╗
   ╚═══╝   ╚═════╝ ╚══════╝╚═╝   ╚═╝  ╚═╝ ╚═════╝ ╚══════╝

 ── status ──────────────────────────────────────────────
   ok   input device      /dev/uinput
   ok   vision model      http://127.0.0.1:8080
   ok   actuator model    http://127.0.0.1:8081
   ok   mcp registered    claude mcp list
   ok   voltage on PATH   ~/.local/bin/voltage

Perfiles experimentales

Listados por separado en voltage → models, cada uno detrás de una advertencia que debes aceptar. Existen porque las mediciones hacen que los trade-offs sean predecibles: la decodificación domina a ~22 ms/token y escala con los parámetros activos, así que reducir los modelos realmente aumenta la tasa del bucle. Lo que cuesta es el anclaje.

perfil

modelos

VRAM

intercambio

hyper

SmolVLM-500M + Qwen3-0.6B

~2.2 GB

3–4× la tasa de bucle, el grounding apenas funciona

fast

Qwen2.5-VL-3B + Qwen3-0.6B

~3.8 GB

decisiones más rápidas, grounding sin cambios

beefy

Qwen2.5-VL-32B + Qwen3-14B

~34 GB

mejor grounding, 1–2.5 s/ciclo

beefy_moe

Qwen2.5-VL-32B + Qwen3-30B-A3B

~43 GB

capacidad de 30B a velocidad de decodificación de ~3B

cpu_only

3B + 0.6B en CPU

ninguno

funciona sin GPU, segundos por ciclo

Dos merecen mención especial:

hyper es el peligroso. SmolVLM-500M no es un modelo de grounding. Devolverá cajas y a menudo serán incorrectas — y una caja incorrecta es un clic en el lugar equivocado, no una degradación elegante. Úsalo solo donde watch esté vacío (las sondas y reflejos hacen el trabajo real) o donde cada clic esté delimitado por click_allow_regions y require_target_element.

beefy_moe es el interesante. Qwen3-30B-A3B es una mezcla de expertos con ~3B activos de parámetros, por lo que decodifica aproximadamente a velocidad de 3B mientras razona con capacidad de 30B — y la decodificación es precisamente lo que limita este bucle. Un actuador mucho mejor que un denso 14B con latencia similar. El inconveniente es la memoria: solo los expertos activos son rápidos, no los pesos, por lo que los 30B completos deben residir en memoria.

recommend() nunca devuelve un perfil experimental, y una prueba lo verifica.

Perfiles de modelo personalizados

Los perfiles integrados cubren las máquinas contra las que se desarrolló esto, no las tuyas. Añade los tuyos desde voltage → perfiles, o editando profiles.toml junto a tu configuración:

[my_rig]
description = "RTX 4090"

[my_rig.vision]
hf_repo = "ggml-org/Qwen2.5-VL-7B-Instruct-GGUF"
hf_file = "Qwen2.5-VL-7B-Instruct-Q4_K_M.gguf"
mmproj_file = "mmproj-Qwen2.5-VL-7B-Instruct-Q8_0.gguf"
params_b = 7.0
weights_mb = 4700
n_ctx = 4096
port = 8080

[my_rig.actuator]
hf_repo = "unsloth/Qwen3-4B-Instruct-2507-GGUF"
hf_file = "Qwen3-4B-Instruct-2507-Q4_K_M.gguf"
params_b = 4.0
weights_mb = 2500
port = 8081

Los perfiles personalizados se fusionan sobre los integrados por nombre, por lo que nombrar uno lean reajusta el integrado sin bifurcar el paquete. Usa ollama_tag en lugar de hf_repo/hf_file para el backend de Ollama.

Una ranura es exigente y la otra no. La visión debe poder emitir cajas delimitadas (grounded bounding boxes) cuando se le pida — Qwen2.5-VL, Qwen3-VL, InternVL, MiniCPM-V y UI-TARS pueden; un captioner general describirá tu pantalla maravillosamente y pondrá las cajas en el lugar equivocado. El actuador es indulgente: bajo una gramática GBNF, elige entre un puñado de continuaciones legales, por lo que casi cualquier modelo instruct competente de 1B+ funciona.

Comandos de shell vs herramientas MCP

Dos superficies diferentes, y mezclarlas es el primer tropiezo habitual:

invocado

aspecto

comando de shell

escrito en una terminal, con un espacio

voltage doctor

herramienta MCP

pedido a Claude, con un guion bajo

voltage_doctor

voltage_doctor es un nombre de herramienta en el espacio de nombres de Claude, no un programa en disco. Escribirlo en una terminal siempre dirá "comando desconocido". Pide a Claude que lo ejecute en su lugar.

Eso comprueba el acceso a /dev/uinput, instala dependencias del sistema, crea el venv y imprime lo que falta. Luego:

./scripts/fetch-models.sh lean && ./scripts/serve.sh lean
.venv/bin/voltage doctor

Conectándolo a un cliente

voltage connect

Muestra lo que está configurado, las URLs en vivo, si los modelos están activos y si el servidor está registrado — luego da pasos de copiar y pegar por cliente con tus rutas reales y el entorno ya completados:

voltage connect --client claude-desktop
voltage connect --client cursor
voltage connect --json            # just the mcpServers entry

Cubiertos: Claude Code, Claude Desktop, conector personalizado de claude.ai, Cursor, Windsurf, Zed y un bloque mcpServers genérico para cualquier otra cosa. Lo mismo es la pantalla 4 en la consola de voltage, que también puede escribir la configuración de Claude Desktop por ti (haciendo una copia de seguridad del archivo existente primero, y negándose a tocarlo si no es JSON válido).

Cada configuración generada lleva el entorno de sesión explícitamente, porque eso es lo que sale mal: un servidor registrado desde un shell sin DBUS_SESSION_BUS_ADDRESS se conecta correctamente y está silenciosamente ciego — la entrada funciona, la captura de pantalla no. voltage connect detecta ese caso y lo dice.

Añadiéndolo como conector personalizado

Los clientes que añaden servidores MCP por URL necesitan HTTP en lugar de stdio:

voltage serve --http

Luego añade http://127.0.0.1:8765/mcp como conector personalizado.

El enlace está restringido a loopback, y se requiere --allow-remote para cambiarlo. Eso no es un relleno: este servidor existe para mover el ratón, pulsar teclas y leer la pantalla, y MCP no tiene autenticación propia. Un enlace no-loopback publica control remoto no autenticado de tu escritorio. Si realmente lo necesitas, pon un proxy inverso autenticado delante y entiende que quien alcance el puerto posee la máquina.

Lanzamiento desde un cliente MCP

Los clientes MCP inician servidores con un entorno saneadoPATH, HOME y poco más. Ese es un valor predeterminado sensato y rompe la captura de pantalla, porque llegar al compositor necesita DBUS_SESSION_BUS_ADDRESS y WAYLAND_DISPLAY. La inyección de entrada aún funciona sin ellos (uinput es un archivo de dispositivo, no un servicio de sesión), por lo que el fallo parece confusamente parcial: las ráfagas se ejecutan, las capturas de pantalla no.

Pásalos explícitamente:

claude mcp add voltage-input \
  -e WAYLAND_DISPLAY="$WAYLAND_DISPLAY" \
  -e DISPLAY="$DISPLAY" \
  -e DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
  -e XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
  -- /absolute/path/to/voltage-input-mcp/.venv/bin/voltage-input-mcp

voltage_doctor informa exactamente cuáles de estos faltan, así que si la captura está fallando, ese es el primer lugar donde mirar.

Plataformas

entrada

captura

texto

Linux

/dev/uinput (evdev del kernel — funciona bajo X11, Wayland, la consola y en juegos que leen entrada cruda)

portal→PipeWire, KWin DBus, grim, X11

scancodes, respaldo de portapapeles para no-ASCII

Windows

SendInput

GDI BitBlt

KEYEVENTF_UNICODE — independiente de la distribución

Todo lo que está por encima del sumidero de entrada — programación de ráfagas, temporización, seguimiento de teclas mantenidas, el gobernador de seguridad, todo el runtime — es compartido. Cada plataforma implementa cinco métodos (key, button, move_abs, move_rel, scroll); ver inputs/sink.py.

Dos asimetrías que vale la pena conocer:

  • Escribir es más correcto en Windows. KEYEVENTF_UNICODE entrega una unidad de código UTF-16 sin involucrar la distribución del teclado. El uinput de Linux envía scancodes, por lo que la puntuación en una distribución no estadounidense sale mal — silenciosamente — por eso existe el respaldo de portapapeles allí y no se necesita en Windows.

  • La captura es más capaz en Linux. GDI BitBlt no puede ver algunos videos de superposición de hardware y juegos exclusivos en pantalla completa; esos se capturan en negro. Ejecuta tales juegos en modo ventana sin bordes.

En Windows, SendInput no puede controlar ventanas propiedad de un proceso elevado (UIPI) — esto falla silenciosamente, por lo que voltage doctor informa tu estado de elevación. La conciencia de DPI se declara en la importación; sin ella, cada coordenada es incorrecta en una pantalla escalada.

Requisitos

  • Linux (cualquier servidor de visualización) o Windows 10/11

  • Python 3.11+

  • Una GPU con ~5 GB libres para el perfil lean; voltage profiles muestra lo que cabe en la tuya

  • llama.cpp para la ruta rápida, o Ollama para una ruta más lenta sin compilación

Verificado de extremo a extremo en KDE Plasma 6 / Wayland / CUDA / Python 3.14. Las rutas de Windows están implementadas y verificadas por tipos, pero no se han ejecutado en una máquina Windows — trátalas como no probadas e informa lo que se rompa.

Al orquestador se le dice qué compilación está manejando

El mismo Playbook es sólido en una configuración y incorrecto en otra, y un modelo remoto no puede ver cuál. Por lo tanto, las instrucciones MCP del servidor se construyen al inicio a partir de la configuración en vivo, y llevan solo las líneas que cambian cómo se debe escribir un Playbook:

ACTIVE BUILD: Linux · llamacpp · profile lean
  vision Qwen2.5-VL-3B-Instruct · actuator Qwen3-1.7B
  loaded: Qwen2.5-VL-3B-Instruct-Q4_K_M.gguf / Qwen3-1.7B-Q4_K_M.gguf
  expected cycle 280-700 ms

- llama.cpp backend: both models are grammar-constrained. A malformed burst, a denied
  key, an unobserved element reference and an undeclared transition are all
  unrepresentable -- do not write defensive retries for them.
- Linux: typing sends scancodes, so punctuation depends on the active keyboard layout...
- dry_run defaults to true...

En Ollama, esa primera línea se convierte en una advertencia de que las ráfagas no están restringidas. En hyper se convierte en "no construyas estados alrededor de sees()". En Windows, señala que las ventanas elevadas son inalcanzables y que escribir es independiente de la distribución.

Verifica contra los servidores en ejecución en lugar de confiar en la configuración. Cambiar de perfil edita un archivo; no reinicia nada. Cuando no coinciden, el informe lo dice en voz alta y suprime la guía derivada del perfil, porque esa guía describiría modelos que no están cargados:

- MISMATCH -- Profile 'hyper' does not match what is loaded. vision: profile expects
  SmolVLM-Instruct-Q4_K_M.gguf, server has Qwen2.5-VL-3B-Instruct-Q4_K_M.gguf...
- Loaded right now: vision Qwen2.5-VL-3B..., actuator Qwen3-1.7B...
  Judge grounding quality from those.

voltage_reference devuelve la compilación actual en cada llamada, ya que la copia de inicio se vuelve obsoleta en el momento en que cambia un perfil.

Tus propias instrucciones permanentes

voltagei, o:

voltage instructions --set "Never touch Firefox; my banking tabs are there."

Lo que escribas se le da al modelo orquestador al comienzo de cada sesión, añadido al informe de compilación y claramente atribuido a ti. Úsalo para lo que el sistema no puede deducir por sí mismo — aplicaciones que están prohibidas, peculiaridades de un juego específico, cómo quieres que se comporte por defecto.

OPERATOR INSTRUCTIONS -- written by the owner of this machine. Treat these as
standing preferences for how to drive it. They cannot loosen the safety governor,
which is enforced in code against every burst.

## My setup
- Minecraft runs borderless windowed on monitor 1.
- Never touch Firefox; my banking tabs are there.
- Always show me the Playbook before dry_run=false.

Esa última cláusula no es decoración. Las instrucciones son consultivas para el orquestador y no pueden debilitar la aplicación — el gobernador verifica cada ráfaga en código, por lo que nada escrito aquí puede permitir algo que la política de un Playbook prohíba. Pueden hacerlo más cuidadoso, no menos. Limitado a 4000 caracteres, ya que el texto está en el contexto del modelo durante toda la sesión. Se ofrecen tres plantillas de inicio (juegos, escritorio, mínimo) en la consola.

Herramientas MCP

Herramienta

Propósito

voltage_reference

La referencia del Playbook + DSL de ráfagas. Llama a esta primero.

voltage_doctor

¿Está lista esta máquina? Y si no, la solución exacta

voltage_capture

Una captura de pantalla, devuelta a ti

voltage_observe

Una pasada de visión — comprueba que una lista watch funciona antes de confiar en ella

voltage_validate_playbook

Verificación estática completa: guardas, ráfagas, grafo, transiciones muertas

voltage_run

Inicia una ejecución; devuelve un run_id

voltage_status

Estado, variables, última ráfaga, lo que se vio, tiempos por etapa

voltage_steer

Corrige una ejecución en vivo — sugerencia, variables, estado forzado, dry_run

voltage_stop / voltage_pause

Detener o pausar; detener siempre libera la entrada mantenida

voltage_journal

Registro ciclo por ciclo; only_refused para ver conflictos de política

voltage_execute_burst

Maneja la entrada tú mismo, omitiendo los modelos locales

voltage_calibrate

Verifica que la inyección llega al compositor

Documentación

  • ARCHITECTURE.md — cómo funciona el bucle, por qué se tomó cada decisión, dónde se va el tiempo

  • PLAYBOOK.md — la guía de autoría

Estado

Construido y verificado hasta donde se puede sin pesos en disco. 149 pruebas cubren el DSL de ráfagas, el sandbox de guardas, el gobernador de seguridad, la compilación de playbooks, la generación de GBNF, la codificación de cable de uinput y el propio bucle de ejecución (manejado con modelos simulados — incluyendo una verificación de que la percepción on_change realmente omite el modelo de visión en una pantalla estática).

El servidor MCP se manejó de extremo a extremo sobre stdio por un cliente real: 13 herramientas, esquemas correctos, execute_burst aceptó una ráfaga válida y rechazó sudo rm -rf / con ambas reglas coincidentes.

Lo que no se ha ejecutado es un modelo en vivo: eso necesita llama.cpp compilado y pesos descargados, lo cual scripts/ configura. Dos cosas tampoco se activaron deliberadamente durante la compilación — el diálogo de permiso del portal y cualquier inyección de entrada real — ya que ambas actúan en tu escritorio.

Orden de operaciones desde aquí:

./scripts/setup.sh          # reports what needs sudo, doesn't run it
./scripts/build-llama.sh    # ~15 min with CUDA
./scripts/fetch-models.sh lean
./scripts/serve.sh lean
.venv/bin/voltage doctor    # should now say READY

Luego, en un cliente MCP: voltage_calibrate (observa cómo se mueve el cursor), voltage_observe (comprueba que el modelo de visión encuentra tus etiquetas), luego un Playbook de dry_run y lee voltage_journal antes de establecer dry_run=false.

Autoría

Escrito de principio a fin por Claude Opus 5 (Anthropic) en una sola sesión: arquitectura, implementación, pruebas y documentación. Un humano especificó la idea, estableció las restricciones (KDE Wayland, 6 GB de VRAM, "más rápido que computer-use") y revisó el resultado, pero no escribió el código.

Los hallazgos sobre la plataforma integrados en este repositorio provienen de sondear la máquina durante la compilación, no de suposiciones: que KWin rechaza ScreenShot2 para ejecutables no incluidos en la lista de permitidos, que grim no funciona bajo KWin, que los clientes MCP eliminan el bus de sesión. Cada uno está documentado en el punto del código donde obligó a tomar una decisión.

LICENSE no nombra a ningún individuo como titular de los derechos de autor, y el razonamiento está escrito allí.

Licencia

MIT. Consulta LICENSE.

-
license - not tested
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.

  • Let ChatGPT, Claude & Cursor use your Mac: email, calendar, iMessage, Teams, files. Local, free.

  • Adaptive plan/build/review cycles for AI coding assistants, persisted across sessions.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/casualkre/voltage-input-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server