Electron Viewer

io.github.acertijosappsPublished by GitHub account acertijosappsv1.1.0Updated Nov 7, 2026

Drive Electron apps over CDP: screenshots, clicks, drags, keys, JS in renderer and main, logs.

VerifiedSTDIODesktop onlyBrowser AutomationDeveloper Tools

Overview

AI-generated overview

Drives Electron desktop apps over CDP: screenshots, mouse, touch and keyboard input, JavaScript in the renderer and main process, and console logs.

What you can do
  • List, launch and attach to Electron apps and their windows
  • Capture window screenshots and read the accessibility tree with click-ready coordinates
  • Click, drag, hover, scroll, type text and press keys with modifiers
  • Tap, swipe, long press and pinch for touch-first apps
  • Resize or reload the window, and run JavaScript in the page or the main process
  • Read console messages and exceptions, or the raw stdout of a launched app
When to use it
Useful when an assistant needs to inspect, test or operate a local Electron app: reproducing UI bugs, checking a flow end to end, or driving a kiosk or touch app. It also lets a human watch the same window live in a browser pane while the agent works.
Requirements
Runs locally over stdio via npx; Node.js 18 or later. Needs an Electron project with electron in node_modules to launch, or an app already started with a remote debugging port. Optional environment variables: ELECTRON_VIEWER_PROJECT, ELECTRON_VIEWER_URL, ELECTRON_VIEWER_LAUNCH_CMD. Desktop only; no authentication.
Before you install
The server can run arbitrary JavaScript in the app's renderer and main process, so it can read or change anything the app can. It launches processes and sends real input that writes and deletes data in the app. Pin an exact package version rather than letting npx fetch the latest. Native UI such as menus and dialogs is not captured or clickable.

Installation

In SourceWeft

  1. Open Electron Viewer in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

@acertijosapps/electron-viewer

Live viewer for Electron apps in the browser, plus an MCP server so an AI agent can drive them. Built for the browser pane of AI coding tools (such as Claude Code): you and the agent watch and control the same app window, in real time.

[Electron Viewer showing the example app: window selector and launch button on top, the live window in the middle, and the reload, DevTools, window size, touch and screenshot controls at the bottom]
  • Live video: Page.startScreencast from the Chrome DevTools Protocol (CDP) over WebSocket. It keeps streaming while the app window is covered or minimized.
  • Real input: mouse move and hover, click, double click, right click, drag, wheel and full keyboard passthrough (including ⌘C/⌘V/⌘A on macOS, IME and dictation).
  • Touch: a touch mode in the viewer turns the pointer into a finger, and the agent can tap, swipe, long press and pinch, for touch-first and kiosk apps.
  • Every app and window: launch your project with electron . from the viewer, or attach to any app started with --remote-debugging-port. Apps on ports 9222–9231 are found automatically.
  • Main process too: launched apps also expose their main process (Node inspector), so the agent can run code there, resize the real window and open DevTools.
  • MCP server: the same actions as tools, plus JavaScript in the renderer and main process, the accessibility tree with click-ready coordinates, and console logs.
  • Nothing to add to your app: no preload, no dependency, no code changes.

Requirements

  • Node.js 18 or later.
  • An Electron app. To launch it from the viewer, the project must have electron in its node_modules (npm install --save-dev electron); if its binary hasn't been downloaded yet, the viewer downloads it on the first launch.
  • Works on macOS, Linux and Windows.

Quick start

From your Electron project folder:

sh
npx @acertijosapps/[email protected]

Open http://localhost:8094 and click Launch. The viewer runs electron . on free debugging ports (CDP from 9222, Node inspector from 9229). Deep link to a window: ?window=<target id>.

If your app is started by another tool (electron-vite, Electron Forge, a custom script), give the viewer your own start command. {cdpPort} and {inspectPort} are replaced with free debugging ports, and the Launch button (and launch_app) runs it in the project folder:

sh
npx @acertijosapps/[email protected] --launch-cmd "yarn dev --remoteDebuggingPort {cdpPort} --inspect {inspectPort}"

That line is for electron-vite; for an npm script that ends in electron ., pass the flags after --: --launch-cmd "npm start -- --remote-debugging-port={cdpPort} --inspect={inspectPort}". The command is only read from the command line or ELECTRON_VIEWER_LAUNCH_CMD, never from the HTTP API or the MCP.

Or start the app yourself with the flags and the viewer finds it:

sh
electron --remote-debugging-port=9222 --inspect=9229 .

Apps on CDP ports 9222–9231 are found automatically, and so is their main process when its inspector is on 9229–9238. --inspect is optional; without it you get everything except the main-process features (evaluate_main, real window resizing and in-app DevTools). To attach on other ports, use the form in the viewer or --attach and --inspect.

text
electron-viewer [options]        Start the viewerelectron-viewer mcp [options]    Start the MCP server on stdio
      --project <dir>      Project to launch with "electron ." (default: current folder)      --launch-cmd <cmd>   Launch with this shell command instead; {cdpPort} and {inspectPort}                           are replaced with the debugging ports  -p, --port <port>        Viewer port (default 8094, env PORT)      --host <host>        Interface to bind (default 127.0.0.1, env HOST)      --attach <port>      Attach at startup to an app on this CDP port      --inspect <port>     With --attach: the app's main process inspector port      --viewer-url <url>   Viewer the MCP talks to (default http://localhost:8094)

Other environment variables: MAX_DIM (longest side of the streamed frames, default 1920), QUALITY (JPEG quality, default 80), SCREENSHOT_MAX (longest side of MCP screenshots, default 1024), CDP_PORT / INSPECT_PORT (first ports to scan and launch on, default 9222 / 9229) and ELECTRON_VIEWER_STATE_DIR (where launched and attached apps are remembered, default ~/.cache/electron-viewer).

Use it with an AI agent (MCP)

Claude Code:

sh
claude mcp add electron-viewer -- npx -y @acertijosapps/[email protected] mcp

Any other MCP client:

json
{  "mcpServers": {    "electron-viewer": {      "command": "npx",      "args": ["-y", "@acertijosapps/[email protected]", "mcp"]    }  }}

Also listed in the official MCP Registry as io.github.acertijosapps/electron-viewer.

The examples pin an exact version. The viewer and the MCP can run code in your app, so pin the version you reviewed instead of letting npx fetch whatever was published last, and update it on purpose.

Keep the viewer running (npx @acertijosapps/[email protected]) to watch what the agent does. Without it, the MCP drives the app over CDP directly. Apps launched by either one are visible to the other.

ToolWhat it does
list_windowsRunning apps (with main when the main process is reachable) and their windows
launch_appRun electron . in the project on free debugging ports and wait for the first window
attachAttach to an app on a CDP port, optionally with its inspector port
screenshotPNG of the window content, longest side ≤ 1024 px
clickLeft, right or middle click; count: 2 for a double click; optional modifiers
dragPress, move in steps and release: a real drag
hoverMove the mouse without clicking
tapOne-finger tap (touch events, pointerType: "touch")
swipeOne-finger swipe: scrolls touch lists and drives swipe gestures
long_pressHold one finger down for a while, 800 ms by default
pinchTwo-finger pinch around a point; scale > 1 zooms in, < 1 zooms out
scrollMouse wheel at a point, deltas in CSS px
type_textInsert text into the focused element (any Unicode)
press_keyEnter, Tab, Escape, arrows, F1–F12… or any character, with modifiers
resize_windowResize the content area: the real window with the main process, an emulated viewport when the window can't reach the size (or with emulate: true)
reloadReload the window's page
evaluateRun JavaScript in the window's page and return the value
evaluate_mainRun JavaScript in the main process, with require('electron') available
get_accessibility_treeRoles, names, values and states; interactive elements carry their normalized center
get_console_logsConsole messages and exceptions from renderer windows and the main process, or the raw stdout

Coordinates are normalized to 0–1 over the content screenshot (x = pixel_x / image_width), so they hold at any window size. window is optional when only one window is open; it takes the target id from list_windows or part of the window title.

In the Claude Code browser pane

Add the viewer to .claude/launch.json and open it with preview_start electron-viewer:

json
{  "version": "0.0.1",  "configurations": [    {      "name": "electron-viewer",      "runtimeExecutable": "npx",      "runtimeArgs": ["-y", "@acertijosapps/[email protected]"],      "port": 8094,      "autoPort": false    }  ]}

HTTP API

The MCP uses the viewer's API; you can call it too. window (target id or part of the title) goes in the query string or the body and is optional with a single window. The server only listens on 127.0.0.1, only accepts a local Host and origin, and POST requests require content-type: application/json, so other websites can't drive your apps.

Endpoint
GET /api/windows[{ id, name, cdpPort, inspectPort, main, windows: [{ id, title, url }] }]
GET /api/projectThe project the viewer launches and whether it has Electron installed
GET /api/screenshot?max=<px>PNG; viewport size in X-Viewport-Width / X-Viewport-Height
GET /api/ax-tree?interactiveOnly=true{ tree }
GET /api/logs?level=&source=&limit={ logs } or { stdout }
POST /api/launch{ project? }
POST /api/attach{ port, inspectPort? }
POST /api/click{ x, y, button?, count?, modifiers? }
POST /api/drag{ x1, y1, x2, y2, ms? }
POST /api/hover{ x, y }
POST /api/tap{ x, y }
POST /api/swipe{ x1, y1, x2, y2, ms? }
POST /api/long-press{ x, y, ms? }
POST /api/pinch{ x?, y?, scale, ms? }
POST /api/scroll{ x, y, dx, dy }
POST /api/text{ text }
POST /api/key{ key, modifiers? }
POST /api/resize{ width, height, emulate? } (0 × 0 restores the window size)
POST /api/reload{ ignoreCache? }
POST /api/evaluate{ expression }
POST /api/evaluate-main{ expression, app? }
POST /api/devtoolsToggles DevTools; without the main process returns the DevTools url

How it works

  • Everything goes through CDP with ws: no Playwright, no Puppeteer, nothing injected into the app. The renderer is reached through --remote-debugging-port (one page target per window) and the main process through the Node inspector (--inspect), where the console API provides require.
  • One screencast session per window, shared by every viewer tab and stopped when the last one closes. The last frame is kept for late joiners. Focus and page lifecycle are emulated while someone is watching, so a covered or minimized window keeps painting.
  • Viewer input is queued in order and mouse moves are coalesced to one per animation frame. A hidden textarea holds the focus: keys are sent with Input.dispatchKeyEvent, and text that arrives without key events (IME, dictation) with Input.insertText. On macOS, ⌘A, ⌘C, ⌘V, ⌘X and ⌘Z are sent as editing commands, because the native menu that normally handles them never sees CDP events.
  • Touch goes through Input.dispatchTouchEvent. The first touch action on a window also turns on touch emulation (navigator.maxTouchPoints 5, pointer: coarse) until the viewer or MCP that sent it exits.
  • Each process keeps one connection per window and per main process, which also collects console messages and exceptions (the last 1000 per app).
  • For an app with no known inspector, each Node inspector on ports 9229–9238 is asked once, read only, whether it is Electron and which CDP port its app listens on. Any other Node process started with --inspect in that range gets one short connection (it logs Debugger attached.) and is left alone.
  • Launched and attached apps are remembered in ~/.cache/electron-viewer, so the viewer and the MCP see the same apps. Launched apps write their output to a log file there and keep running if the viewer or MCP exits.

Limitations

  • CDP only sees web content. Native UI (title bar, menus, dialogs, tray, notifications) isn't shown or clickable; use evaluate_main to drive it from the main process. Capturing it would mean capturing the screen, and with it any other window on top of the app, so the viewer doesn't.
  • Synchronous dialogs (dialog.showMessageBoxSync) block the main process: evaluate_main waits until the dialog is closed.
  • Each WebContentsView is a separate CDP target, so it is listed as its own window. <webview> tags are not listed.
  • A long press reaches the app as a held touch, so apps that time touchstart themselves see it, but Chromium on desktop doesn't turn it into a context menu. The viewer's touch mode is one finger; use pinch for two.
  • No audio.
  • A --launch-cmd must pass the ports on to Electron; how depends on the tool. Launched apps keep running when the viewer exits; close them from the app (or its terminal).

Contributing

Branching model (main · beta), Conventional Commits and releases are described in CONTRIBUTING.md; changes are listed in CHANGELOG.md. The example app in example/ is there to try changes.

Author

Made by AcertijosApps. See the rest of our libraries at acertijosapps.com/librerias.

Questions or proposals: [email protected] or open an issue.

License

MIT

Source: README.md at commit 980e6e6

Tools

20

list_windows

List windows

Lists the running Electron apps (found on CDP ports 9222–9231, launched or attached) and their windows. main=true means the main process is reachable (evaluate_main, real window resizing). Includes the viewer URL to watch a window live in a browser pane.

Takes no parameters.

launch_app

Launch app

Starts the Electron project on free debugging ports, so both the renderer (CDP) and the main process (Node inspector) are reachable, and waits for the first window. It runs `electron .` with the project's own Electron, or the launch command configured with --launch-cmd (e.g. a dev server).

Parametersproject

attach

Attach to app

Attaches to an Electron app already running with --remote-debugging-port=<port> (and optionally --inspect=<inspect_port> for the main process). Apps on ports 9222–9231 are found without attaching.

Parametersportrequiredinspect_port

screenshot

Screenshot

Captures the window content (PNG). For click, drag, hover and scroll use normalized coordinates: x = pixel_x / image width, y = pixel_y / image height. Native UI (title bar, menus, dialogs) is not included.

Parameterswindow

click

Click

Clicks a point of the window content (Input.dispatchMouseEvent). count=2 is a double click.

Parametersxrequiredyrequiredcountbuttonwindowmodifiers

drag

Drag

Presses the left button at one point, moves to another with intermediate steps and releases (a real drag).

Parametersx1requiredx2requiredy1requiredy2requiredwindowmodifiersduration_ms

hover

Hover

Moves the mouse to a point without clicking (hover effects, tooltips).

Parametersxrequiredyrequiredwindow

tap

Tap

Taps a point with one finger (Input.dispatchTouchEvent): the page gets touch and pointer events with pointerType "touch". The first touch action turns on touch emulation for the window (maxTouchPoints, pointer: coarse).

Parametersxrequiredyrequiredwindow

swipe

Swipe

Swipes one finger from one point to another (touch events; use drag for the mouse). Scrolls touch lists and drives swipe gestures.

Parametersx1requiredx2requiredy1requiredy2requiredwindowduration_ms

long_press

Long press

Holds one finger on a point and releases it. Apps that time touchstart themselves see a long press; Chromium on desktop doesn't turn it into a context menu.

Parametersxrequiredyrequiredwindowduration_ms

pinch

Pinch

Two-finger pinch centered on a point: scale > 1 spreads the fingers (zoom in), scale < 1 brings them together.

Parametersxyscalerequiredwindowduration_ms

scroll

Scroll

Scrolls with the mouse wheel at a point. Positive delta_y scrolls down; deltas are in CSS pixels.

Parametersxywindowdelta_xdelta_y

type_text

Type text

Types text into the focused element (Input.insertText; any Unicode). Click the field first; for Enter or shortcuts use press_key.

Parameterstextrequiredwindow

press_key

Press key

Presses a key, optionally with modifiers (e.g. key "a" + ["meta"] selects all on macOS). Named keys: Enter, Tab, Backspace, Delete, Escape, Space, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown, Insert, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12; or any single character.

Parameterskeyrequiredwindowmodifiers

resize_window

Resize window

Sets the size of the window content area in CSS px. With the main process it resizes the real window; if the window cannot reach that size (smaller screen, kiosk, full screen), without the main process or with emulate=true, the page viewport is emulated at the exact size instead. Width and height 0 restore the window size.

Parameterswidthrequiredheightrequiredwindowemulate

reload

Reload

Reloads the page of the window (renderer only; the main process keeps running).

Parameterswindowignore_cache

evaluate

Evaluate in renderer

Evaluates a JavaScript expression in the window page (main world) and returns its JSON value. Promises are awaited.

Parameterswindowexpressionrequired

evaluate_main

Evaluate in main process

Evaluates a JavaScript expression in the app's main process (Node inspector) and returns its JSON value. `require` is available, e.g. require('electron').BrowserWindow.getAllWindows().map(w => w.getTitle()). Promises are awaited. Needs main=true in list_windows.

Parametersappexpressionrequired

get_accessibility_tree

Accessibility tree

Returns the accessibility tree of the window as indented text (role "name" value [state]). Interactive elements carry @(x, y): their center, normalized like click coordinates.

Parameterswindowinteractive_only

get_console_logs

Console logs

Console messages and uncaught exceptions of the app: renderer windows and main process (up to the last 1000). source "stdout" returns the raw output of an app started with launch_app.

Parametersapplevellimitsourcewindow

Version history

2
  1. v1.1.0LatestNov 4, 2026
  2. v1.0.0Nov 3, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts