Electron Driver

Impulsa aplicaciones Electron desde agentes de IA mediante MCP: haz clic, escribe, arrastra, captura pantallas, evalúa JS y más.

Documentación

electron-driver

npm version license node version

Impulsa aplicaciones Electron desde agentes de IA. Haz clic, escribe, arrastra, captura pantallas, evalúa JavaScript en el proceso renderizador o principal, lee registros de consola, maneja aplicaciones multi-ventana, captura instantáneas de accesibilidad — todo a través de un servidor MCP (Protocolo de Contexto de Modelo) que se conecta a Claude Code, Claude Desktop, Cursor y cualquier otro host de agente compatible con MCP.

https://github.com/user-attachments/assets/a95500d2-28d2-4ee1-9965-8f7e1ef54caa

Construido sobre la API experimental _electron de Playwright. Funciona con cualquier aplicación Electron — React, Vue, Svelte, vanilla — siempre que puedas apuntarlo a un punto de entrada del proceso principal compilado.

Estado: v0.3.0. Primera versión pública. 38 herramientas que cubren flujos de trabajo reales.

Por qué existe esto

Los agentes de IA pueden razonar sobre lo que una aplicación de escritorio debería hacer, pero no pueden verla ni interactuar con ella por sí solos. Los navegadores web tienen muchas opciones de automatización para agentes; Electron casi no tiene ninguna. Este paquete cierra esa brecha: dale al agente la ruta a tu aplicación Electron compilada y podrá manejarla de la misma manera que lo haría un humano.

Casos de uso comunes:

  • Un agente verifica una característica que acaba de implementar ejecutando realmente la aplicación y comprobando el resultado visible
  • Pruebas de regresión visual durante una refactorización
  • Auditorías de accesibilidad mediante instantáneas del árbol ARIA
  • Reproducción de errores a partir de una descripción en lenguaje natural
  • Enseñar a un subagente a iterar sobre la interfaz hasta que una especificación pase

Instalación

Requiere Node 18+ y una aplicación Electron que ya hayas compilado.

npm install electron-driver

No necesitas instalar los navegadores de Playwright por separado — _electron maneja directamente tu binario de Electron.

Regístrate con tu host de agente

Claude Code (ámbito de proyecto)

Crea .mcp.json en la raíz del repositorio:

{
  "mcpServers": {
    "electron-driver": {
      "command": "npx",
      "args": ["electron-driver"]
    }
  }
}

Claude Code (ámbito de usuario — disponible en todos los proyectos)

claude mcp add electron-driver --scope user -- npx electron-driver

Claude Desktop / Cursor / otros

Añádelo a la configuración MCP del host, apuntando a npx electron-driver o a la ruta absoluta de node_modules/electron-driver/index.mjs.

Idea central

El servidor posee exactamente una sesión de Electron a la vez. start_app la inicia, todo lo demás la maneja, stop_app la cierra. Las capturas de pantalla van a un directorio de sesión que se borra en cada start_app — sin acumulación, sin artefactos obsoletos. Todas las llamadas a herramientas se registran en <project>/.electron-driver/driver.log durante una sesión.

Los errores llevan un campo estable code para que los llamadores puedan ramificar programáticamente sin hacer coincidir expresiones regulares con el texto:

CódigoSignificado
NOT_RUNNINGUna herramienta necesita una sesión en ejecución, pero no hay ninguna
ALREADY_RUNNINGstart_app se llamó mientras existe una sesión
TIMEOUTUna acción alcanzó su tiempo de espera
NOT_FOUNDEl selector o archivo no coincidió
FILE_NOT_FOUNDLa entrada basada en ruta apunta a un archivo inexistente
BAD_ARGUMENTLos argumentos fallaron la validación
UNKNOWN_TOOLNombre de herramienta no reconocido
ERRORTodo lo demás

Herramientas

Las 38 herramientas agrupadas por propósito. Cada herramienta basada en selectores usa el motor de selectores completo de Playwright: CSS, text=, role=, [aria-label=], :has-text(), alcance (main >> button), etc.

Ciclo de vida

start_app — inicia la aplicación. Toma main (ruta absoluta al punto de entrada principal compilado), opcionalmente cwd, args, env, screenshotsDir, timeoutMs. Devuelve { title, url, viewport, screenshotsDir, logFile }. Detecta el modo de fallo del bloqueo de instancia única y da una pista útil en lugar de un error de desconexión crudo.

stop_app — cierra limpiamente. Seguro en una sesión ya detenida.

info{ title, url, viewport: {width, height, devicePixelRatio}, uptimeMs }. El viewport se rellena desde window.innerWidth/innerHeight.

Captura

screenshot — PNG de página completa. Pasa name (sin extensión) para controlar el nombre del archivo. Devuelve { path }.

cleanup_screenshots — borra el directorio de capturas de la sesión actual.

console_logs — mensajes recientes de la consola del renderizador (log/info/warn/error/debug/pageerror) y salida estándar/error del proceso principal. Buffer rotativo de 1000 entradas. Filtra por source (renderer/main/all), type y limit. Pasa clear: true para vaciar después de leer.

Interacción

click — hace clic en un elemento. Opciones: timeoutMs, button (left/right/middle), clickCount, force (omite comprobaciones de capacidad de acción), position (hace clic en un desplazamiento dentro del elemento).

typefill un campo de texto, reemplazando el contenido existente. Rápido pero solo funciona en entradas reales. Para editores/CodeMirror/contenteditables, usa keyboard_type.

keyboard_type — escribe como eventos reales de teclado por carácter. Pasa focusSelector para hacer clic en un elemento primero. Advierte en el resultado si nada tiene el foco y no se pasó un selector de foco.

press — presiona una tecla o combinación: "Escape", "Enter", "Control+S", "Shift+Tab", "Control+Shift+P".

press_sequence — alias de keyboard_type sin selector de foco.

hover — pasa el cursor sobre un elemento. Opciones: timeoutMs, force.

drag — arrastra desde un punto a otro usando eventos de entrada reales de Chromium (a través de la tubería de ratón CDP de Playwright). Como son eventos de navegador confiables, la tubería de puntero de Chromium genera PointerEvents coincidentes como efecto secundario, por lo que React onPointerDown, los listeners nativos de pointerdown, setPointerCapture, CSS :hover/:active y cualquier otro consumidor de puntero ven el arrastre exactamente como si un usuario real lo hubiera realizado. Las coordenadas son píxeles CSS. Pasa detectSelector y el controlador medirá el elemento antes y después del arrastre e incluirá detect.moved en el resultado — la única forma confiable de detectar arrastres que silenciosamente alcanzan un límite mínimo/máximo.

{
  "from": { "x": 275, "y": 400 },
  "to":   { "x": 420, "y": 400 },
  "detectSelector": ".sidebar-resize-handle"
}

Si la estrategia principal no mueve el objetivo de detección, el controlador automáticamente recurre a invocar el manejador de React directamente mediante acceso a la prop del fiber y despachando eventos de mover/soltar tanto en document como en window — cubriendo todos los patrones conocidos de divisores de React. Desactiva el respaldo con fiberFallback: false. El resultado incluye strategy ("pointer-capture" o "react-fiber").

clear_input — vacía una entrada o área de texto.

select_option — selecciona de un <select> por value, label o index.

check — marca una casilla de verificación o botón de opción. Opciones: timeoutMs, force.

uncheck — desmarca una casilla de verificación.

scroll — desplaza un contenedor (pasa selector) o la ventana. Soporta absoluto (x, y) o delta (dx, dy).

scroll_into_view — asegura que un elemento sea visible. Seguro si ya lo es.

drop_file — simula soltar un archivo sobre un objetivo mediante DragEvents sintéticos y un File reconstruido con DataTransfer. Funciona para aplicaciones que leen el archivo a través de APIs web (FileReader, File.text(), etc.). No rellena file.path — las aplicaciones que dependen de webUtils.getPathForFile() deben usar eval_main para invocar su propio manejador IPC directamente.

set_input_files — la forma correcta de probar la interfaz de subida de archivos. Establece archivos en un <input type="file"> sin un diálogo nativo. Mucho más confiable que drop_file cuando la aplicación usa entradas de archivo reales.

Espera

wait — pausa fija en milisegundos. Prefiere las demás.

wait_for_selector — espera hasta que un selector alcance un estado (attached/detached/visible/hidden). Respeta timeoutMs. Devuelve count, box y elapsedMs en éxito; el error lleva elapsed vs requested en tiempo de espera.

wait_for — consulta un predicado de JavaScript (cuerpo de función, usa return) hasta que devuelva un valor verdadero. Opciones: timeoutMs, pollMs.

Comprobación y lectura de estado

exists{ exists, count } comprobación rápida, sin espera. Acepta el motor de selectores completo.

get_text — contenido de texto de la primera coincidencia. Acepta el motor de selectores completo. Devuelve { exists, text }.

get_attribute — lee un atributo HTML por nombre. Devuelve { exists, value }.

get_value — lee el valor actual de una entrada/área de texto/select.

get_bbox — cuadro delimitador como { x, y, width, height } en píxeles CSS. Úsalo antes de arrastrar o hacer clic en un desplazamiento.

get_computed_style — lee una o más propiedades CSS calculadas. Pasa un array de properties.

elements_list — enumera elementos que coinciden con un selector con su etiqueta, id, clases, fragmento de texto, cuadro y atributos clave. Ideal para "qué botones existen en esta pantalla". Limitado a 50 por defecto; ajusta con limit.

focused_element — qué tiene el foco actualmente, con etiqueta/id/clases/texto y cuadro delimitador. Devuelve { focused: false } si nada significativo tiene el foco.

accessibility_snapshot — captura el árbol ARIA como JSON. Útil para auditorías de accesibilidad y encontrar elementos por rol. Pasa interestingOnly: false para incluir cada nodo. Pasa root para capturar un subárbol.

Multi-ventana

windows_list — cada BrowserWindow que la aplicación tiene abierta, con id, título, URL, banderas de foco/visibilidad/estado.

switch_window — enruta llamadas de herramientas posteriores a una ventana diferente. Pasa index o titleMatch.

Diálogos

dialog_handler — instala un auto-respondedor para diálogos de JavaScript (alert/confirm/prompt/beforeunload). Pasa action: "accept" | "dismiss", opcionalmente text para prompt(), y once: true (por defecto) para desinstalar automáticamente después del primer diálogo.

Vías de escape de evaluación

Tanto eval_renderer como eval_main usan el mismo contrato: pasa un cuerpo de función, usa return para devolver un valor, soporta async/await, y una carga útil opcional de arg está disponible como la variable local arg.

eval_renderer — evalúa en el contexto del renderizador (página).

{
  "js": "return document.querySelectorAll(arg.selector).length",
  "arg": { "selector": ".item" }
}

eval_main — evalúa en el proceso principal de Electron. El cuerpo recibe electron (el módulo completo de Electron) y arg.

{
  "js": "return electron.app.getName()"
}
{
  "js": "const w = electron.BrowserWindow.getAllWindows()[0]; w.webContents.send('open-file', arg.path); return true",
  "arg": { "path": "C:/docs/README.md" }
}

Usa eval_main como vía de escape para todo lo que el lado del DOM no puede alcanzar: invocar manejadores IPC, leer rutas de usuario, manejar ventanas secundarias, evitar diálogos nativos para aplicaciones que los usan.

Hoja de referencia de selectores

text=Open File              // exact text match
text=/^Save$/               // regex
[aria-label="Settings"]     // ARIA attribute
role=button[name="Close"]   // ARIA role
button:has-text("Save")     // CSS with text predicate
button.primary              // plain CSS
main >> text=Save           // scoped

Consistencia del motor de selectores

Cada herramienta basada en selectores (click, hover, wait_for_selector, get_text, get_attribute, get_value, get_bbox, exists, elements_list, scroll_into_view, select_option, check, uncheck, set_input_files) pasa por el motor de localizadores completo de Playwright. Cualquier cosa que Playwright acepte, estas herramientas la aceptan — incluyendo text=, role= y :has-text().

Las herramientas que leen el DOM de bajo nivel mediante eval_renderer internamente (get_computed_style, scroll, focused_element, drop_file) usan document.querySelector nativo y solo soportan CSS. Esto está documentado en la descripción de cada herramienta cuando corresponde.

Solución de problemas

"El proceso de Electron salió inmediatamente después del lanzamiento." Otra copia de tu aplicación ya está en ejecución y tomó el bloqueo de instancia única — el segundo proceso sale mediante app.requestSingleInstanceLock(). Cierra la instancia en ejecución (revisa tu barra de tareas y procesos en segundo plano) y reintenta. drag devolvió ok:true pero detect.moved es false. La causa más común es un límite mínimo/máximo en el objetivo (por ejemplo, una barra lateral redimensionable en su MAX_WIDTH). Prueba a arrastrar en la dirección opuesta para confirmar que el pipeline de arrastre funciona. Si realmente no es el límite, el fallback de fibra de React debería capturarlo automáticamente — revisa el campo strategy en el resultado. Si incluso eso devuelve moved:false, usa eval_renderer o eval_main para invocar la API de arrastre propia de la app directamente.

type lanza "Element is not an <input>..." Estás golpeando un botón, un div o un contenteditable. Usa keyboard_type con un focusSelector en su lugar.

click agota el tiempo de espera en un elemento que claramente está ahí. Algo lo está cubriendo — un fondo modal, un tooltip, un toast. Usa exists primero para confirmar el recuento, luego prueba force: true, o usa eval_renderer para comprobar getComputedStyle(el).pointerEvents.

Los diálogos nativos son invisibles. Playwright no puede ver los selectores de archivos a nivel de SO, los diálogos de guardado ni las alertas del sistema. Usa eval_main para invocar el mismo manejador de IPC que usa el botón de tu interfaz. Para diálogos de JavaScript (alert/confirm/prompt), usa dialog_handler para responder automáticamente.

Compilación de desarrollo vs app compilada. Esto ejecuta la app compilada, no la salida del servidor de desarrollo. Ejecuta tu comando de compilación antes de start_app, y recompila

  • reinicia la sesión después de cambios en el código fuente.

Una sesión a la vez. Llamar a start_app mientras una sesión está en ejecución devuelve ALREADY_RUNNING. Llama a stop_app primero.

Registros. Cada llamada de herramienta se registra en <project>/.electron-driver/driver.log mientras una sesión está activa. Útil al depurar por qué un agente se quedó atascado.

Ubicación de capturas de pantalla. Por defecto en <project>/.electron-driver/screenshots, donde <project> es el directorio más cercano que contiene .git o package.json. Anula mediante screenshotsDir en start_app.

Seguridad

Este servidor le da al agente conectado control total sobre una app de Electron, incluyendo ejecución arbitraria de código en el proceso principal (vía eval_main). El proceso principal de Electron tiene acceso sin restricciones a Node.js — sistema de archivos, red, procesos hijos, todo. Esto es por diseño: es lo que hace que el driver sea lo suficientemente potente para manejar apps reales.

Lo que esto significa para ti:

  • Úsalo solo a través de stdio (el valor predeterminado). Nunca expongas este servidor a través de HTTP, WebSocket o cualquier transporte de red. Stdio lo vincula al proceso que lo generó — tu sesión local de Claude Code o Claude Desktop.
  • Confía en el agente. El agente que llama a estas herramientas puede hacer cualquier cosa en tu máquina vía eval_main. Solo conecta agentes en los que confíes.
  • No lo uses en entornos multiinquilino. Esta es una herramienta de un solo usuario, para máquina local. No está diseñada para servidores compartidos, pipelines de CI con entrada no confiable, ni ningún contexto donde el llamador pueda ser adversario.
  • drop_file y set_input_files leen archivos locales y pasan su contenido al renderizador. Las rutas de archivo deben provenir de fuentes confiables.

Si estás ejecutando esto con Claude Code, el perfil de riesgo es el mismo que darle acceso a la terminal a Claude Code (que ya tienes). El driver no añade capacidades nuevas más allá de lo que eval en una terminal podría hacer — solo las hace convenientes para que el agente las use.

Limitaciones conocidas

  • El espacio de nombres _electron de Playwright es oficialmente experimental upstream. Ocasionales tiempos de espera de inicio en máquinas lentas; normalmente reintentar lo soluciona.
  • Desarrollado principalmente en Windows. Mac y Linux deberían funcionar — Playwright los maneja — pero están menos probados. Se aceptan informes de errores.
  • switch_window enruta llamadas posteriores a la ventana seleccionada, pero el búfer de registro de consola se llena desde la ventana inicial. La captura de consola multi-ventana es un elemento planificado para v0.4.
  • drop_file no llena file.path. Las apps que usan webUtils.getPathForFile() deben usar eval_main con su propio IPC.
  • Sin captura de solicitudes de red integrada todavía — planificada para v0.4.

Notas de implementación

Para cualquier persona curiosa o que contribuya:

  • Sesión única de Electron, propiedad del proceso del servidor MCP.
  • Las capturas de pantalla se borran en cada start_app — intencional.
  • Los registros de consola se capturan en un búfer rotatorio de 1000 entradas.
  • Cada llamada de herramienta se registra; los errores llevan un campo code estable.
  • Los mensajes de error se reescriben para atribuirse a la herramienta del driver, no al método subyacente de Playwright.
  • La detección de bloqueo de instancia única se basa en "proceso desconectado dentro de 5s del lanzamiento", que es la forma real de la falla.
  • Las evaluaciones están envueltas en IIFE asíncronas, por lo que return funciona y await funciona.
  • Las cargas útiles de arg se coercionan en el lado del servidor (JSON-parse en cadenas) para proteger contra clientes MCP que serializan campos de argumentos.
  • stderr se usa para mensajes de estado; stdout está reservado para tramas del protocolo MCP.

Contribuciones

Se aceptan issues y PRs. Ejecuta localmente con:

cd electron-driver
npm install
node index.mjs  # stdio MCP server

El servidor registra un banner de listo en stderr y espera tramas MCP en stdin. Pruébalo contra una app de Electron real a través de cualquier cliente MCP.

Licencia

MIT