Skip to content

TheRealSeanDonahoe/electromcp

Repository files navigation

ElectroMCP

Drive any Electron app from your AI. One install.

ElectroMCP demo

CI npm version


What you can do Monday morning

  • Bulk-invite a cohort to Slack and send welcome DMs in 60 seconds — instead of 50 minutes of manual clicking
  • Ship Friday-afternoon Electron features without dread, because tests stop hanging on dialogs you forgot to mock
  • Drive your already-running Slack / Discord / Notion / Linear / VS Code from Claude or Cursor — no API tokens, no OAuth, no code changes to those apps
  • Snapshot every screen of your app with one prompt — instead of 30 manual screenshots
  • Reproduce the IPC bug from yesterday's trace by replaying the recorded sequence

These aren't demos. They're the actual workflows ElectroMCP was built for.

Quick start (5 tools you'll use 80% of the time)

claude mcp add electromcp -- npx electromcp

Then in any AI conversation:

Tool What it does When to use it
app_launch { stubMode: 'auto' } Zero-config launch + auto-stub every native dialog/menu/tray/notification Start of every test session
app_attach_by_name { name: "Slack" } Connect to a running app over CDP Power-user / consumer-app flows
app_vision_read Returns app summary + screenshot + a11y + main-state in one call "What is my app showing right now?"
electron_main_state Full nervous system of your app (windows, IPC, menu, tray) in one call Debugging or pre-flight
slack_bulk_invite { csvPath, channels } Onboard a cohort with welcome DMs Mondays

That's the 80%. The other 88 tools are there when you need them.

Magic moments

"I forgot to mock that dialog"

Old way: test hangs forever, you Ctrl-C, dig through stack trace, add mockDialog, re-run. ElectroMCP: app_launch { stubMode: 'auto' } — every native dialog / menu / tray / notification / shell.openExternal is stubbed at boot. Tests never hang again.

"Which workspace am I even in"

Old way: open Slack, scroll the workspace switcher, count. ElectroMCP: prompt Claude with slack_list_workspaces — back in 200ms.

"Is my main process actually ready"

Old way: add console.log, restart app, scroll terminal. ElectroMCP: electron_main_state returns BrowserWindows + ipcMain handlers + menu tree + Tray instances + locale + theme in one call.

"What did the AI just do to my Slack"

Old way: scroll Slack manually, hope nothing weird happened. ElectroMCP: every recipe call writes to ~/.electromcp/audit.log with timestamp, tool, args, return summary. Receipts.

Three things in one MCP server

  1. Automate any Electron app you didn't write

    • app_attach_by_name finds running Electron processes via ps (Slack, Discord, Cursor, Notion, Linear, …) and attaches over CDP
    • Multi-window, multi-instance, multi-session aware
    • BrowserView shim that Playwright structurally cannot match
  2. Pre-built recipes for popular Electron apps

    • @electromcp/recipes-slack, -discord, -notion, -linear (TradingView lands in v1.1; VS Code + Cursor in v1.2)
    • Security model every recipe inherits: auth_scope allowlist (default empty), PII redaction default, append-only audit log, readonly-by-default, token-leak regression test in CI
  3. Deep dev tooling for your own Electron app

    • electron_main_state — full nervous system in one call
    • Native shell auto-stub at one toggle
    • IPC observability (listen, mock, replay, assert)
    • Auto-update simulation, deep-link harness, perf gating, per-OS visual regression

    The testing surface area Tauri structurally cannot match.

Why now

  • MCP just hit critical mass — Claude Desktop, Cursor, Codex, Gemini, Copilot all speak it
  • Tauri is taking marketing oxygen from Electron — the testability moat needs a flagship demonstration
  • The recipe pattern is unclaimed — first mover wins the convention

Install

claude mcp add electromcp -- npx electromcp

For Claude Desktop / Cursor / Windsurf / Copilot / Gemini configs, the command is the same (npx electromcp); only the config file path differs per AI tool.


Feature matrix

Capability ElectroMCP v2.0 Tauri WDIO Electron Spectron kanishka MCP halilural MCP
Zero-config launch (6 archetypes) partial (Forge/builder only)
Native shell auto-stub (one toggle) — (per-call manual)
Mock substrate (vitest-spy ergonomics)
electron_main_state (one-call snapshot) partial
IPC live observability (listen/traffic/replay) mock only
Auto-update simulation (fake server + scenarios)
Deep-link harness (warm + cold start)
Perf gating + assertions
Visual regression (per-OS baselines + auto-mask) partial partial
Multi-session / partition introspection
BrowserView shim (raw CDP) n/a
Process-name attach (Slack, Discord)
Recipe packages (Slack/Discord/Notion/Linear)
Security primitives (auth_scope + PII + audit + readonly) — recipes only 1 partial
macOS testing broken (no WKWebView WebDriver) ✓ (archived)
Codegen export

Why not Tauri?

Tauri is a great choice for some apps. But the testing story is structurally broken in ways ElectroMCP exposes:

  1. No WKWebView WebDriver on macOS. tauri-driver works on Windows and Linux. macOS testing is third-party hacks against Apple's closed-source webview. ElectroMCP runs the same way on every platform.
  2. No main-process introspection. Tauri's webview-only architecture means there is no equivalent to electron_main_state — no live BrowserWindow registry, no ipcMain handler list, no menu tree, no Tray instance dump. Your testing tool can only see what's inside the webview.
  3. No native shell stub surface. Tauri's native dialogs/menus/notifications are Rust-side. There's no construction-site interception model like ElectroMCP's electron_stub_*. Every native call hangs your test or hits the real OS.

If you stay on Electron, you keep the moat. ElectroMCP is the moat.


Tools

App lifecycle (5)

Tool Purpose
app_launch Launch with zero-config archetype detection. Optional stubMode: 'auto', preset, instanceId.
app_connect Attach to a running app on port or full url.
app_disconnect Disconnect without killing the app.
app_stop Shut down and disconnect.
app_health Ping; auto-reconnect if the page is dead.

Visual + interaction (12)

Tool Purpose
ui_screenshot Full screenshot of the active window.
ui_accessibility_snapshot Full a11y tree — cheaper than a screenshot.
ui_get_dom Raw HTML of the page or an element.
ui_observe Visual state with context for AI reasoning.
ui_click Click by selector or visible text.
ui_click_text a11y-role+name first, text fallback — the semantic locator.
ui_fill Set an input value instantly.
ui_type Type character-by-character.
ui_press Press a keyboard chord.
ui_hover Hover (tooltips, hover states).
ui_scroll Scroll page or element in any direction.
ui_wait_for Wait for selector to appear / disappear / become visible.
ui_get_element Attributes, text, bbox, visibility, enabled state.
ui_act Returns a screenshot so the AI picks the next action.
ui_screenshot_match Per-OS visual baseline diff. Auto-masks titlebar / traffic lights.
app_screenshot_match_docker Optional Docker-normalized baseline (cross-platform-stable).

Windows (3)

Tool Purpose
window_list Every open window: title, URL, dimensions, focus.
window_switch Switch by title substring or index.
window_get_by_title First window matching a title substring.

Debug (5)

Tool Purpose
app_console_logs Renderer console output since launch.
app_evaluate_renderer Run JS in the renderer; get the return.
app_evaluate_main Run JS in main. Gated by ALLOW_MAIN_EVAL=1.
app_crash_status Renderer crash check.
app_crash_reset Clear the crash flag.

Test runner (4)

Tool Purpose
test_run Shell out to any test command, parse pass/fail.
test_run_file Run a single test file.
test_run_grep Run tests matching a name pattern.
test_get_results Parsed results from the last run.

Recording + planning (5)

Tool Purpose
batch_run Plan a multi-step sequence; AI executes one tool at a time.
app_record_har Start HAR network recording.
app_stop_har Flush + save HAR.
app_record_video Start .webm video recording.
app_stop_video Flush + save video.

Electron sugar (10)

Tool Purpose
electron_ipc_send Fire-and-forget IPC from renderer (contextBridge or nodeIntegration fallback).
electron_ipc_invoke_main Invoke an ipcMain.handle handler from MCP and capture the return.
electron_ipc_invoke_renderer Drive ipcRenderer.invoke and capture the return.
electron_main_state One-call structured snapshot: BrowserWindows, ipcMain handlers, menu tree, Tray instances, app event log, locale, theme, isReady.
electron_app_get_path app.getPath(name) — userData, logs, downloads, etc.
electron_app_get_metrics getAppMetrics() + getProcessMemoryInfo() per process.
electron_clipboard_read Read system clipboard.
electron_clipboard_write Write system clipboard.
electron_shell_open_external shell.openExternal with stub: 'capture' | 'noop' | 'real'.
electron_on_crash Wait for renderer crash or timeout.

Native shell auto-stub (8)

Tool Purpose
electron_stub_dialog Stub native OS dialogs by type and matchers (title/buttons).
electron_stub_menu Capture Menu.setApplicationMenu / buildFromTemplate template tree.
menu_click Walk captured menu tree and synthesize a click on "File > Save".
electron_stub_tray Capture every new Tray() instance + listeners.
tray_click Synthesize click / right-click / double-click on a captured Tray.
electron_stub_notification Intercept new Notification(); capture title/body/silent/icon.
notification_assert Assert against the captured notification registry.
electron_stub_shell_open_external Capture shell.openExternal URLs without spawning a browser.

app_launch({ stubMode: 'auto' }) toggles all of the above with sane defaults at boot.

IPC observability (7)

Tool Purpose
electron_ipc_listen Start recording all ipcMain.handle / ipcRenderer.send traffic.
electron_ipc_traffic Return recorded events with timestamps + serialization diagnostics.
electron_ipc_mock Vitest-style handler mock — override ipcMain.handle returns.
electron_ipc_assert Pass/fail matcher: { channel, calledWith?, calledTimes? }.
electron_ipc_replay Replay a recorded trace to reproduce a bug from prod.

Auto-update harness (5)

Tool Purpose
app_update_server_start Fake update server: serves latest.yml (electron-updater) and RELEASES (Squirrel.Windows).
app_update_server_stop Stop the fake server.
app_update_set_scenario Pre-arm: available / downloading / downloaded / corrupt / signature_mismatch / partial_download / rollback.
app_update_simulate Fire the armed scenario into the running app's electron-updater listeners.
app_update_assert_called Assert the updater hit your fake server with expected url/headers/channel/times.
app_squirrel_simulate Relaunch with --squirrel-firstrun / install / update / uninstall argv.

Deep-link harness (5)

Tool Purpose
app_deeplink_register Work around setAsDefaultProtocolClient dev-mode broken-ness on macOS/Linux/Win.
app_deeplink_unregister Reverse the registration.
app_deeplink_fire Warm-start: inject via app.emit('open-url') / 'second-instance'.
app_deeplink_simulate_cold_start Cold-start: relaunch with URL prepended to argv.
app_deeplink_assert Assert scheme/path/params were observed by the app.

Perf (4)

Tool Purpose
app_perf_snapshot Startup time + per-process metrics + RSS/heap + recent FPS sample.
app_perf_trace_start Wraps contentTracing.startRecording with named scenarios.
app_perf_trace_stop Stop and return the trace JSON path.
app_perf_assert expect(startup).toBeUnder(1500)-style matcher with stable-baseline hashing.

Multi-session (4)

Tool Purpose
app_partitions List named sessions with metadata.
partition_snapshot Cookies + localStorage + IndexedDB + serviceWorker registrations.
partition_assert_isolated Assert zero key overlap between two partitions.
partition_clear Bulk reset a partition.

BrowserView + process attach (4)

Tool Purpose
browserview_list List BrowserViews via raw CDP (Playwright doesn't support them).
browserview_evaluate Run JS inside a BrowserView.
browserview_screenshot Screenshot a BrowserView.
app_attach_by_name Find a running Electron process via ps (Slack, Discord, …) and attach over CDP.

Environment overrides (4)

Tool Purpose
app_set_geolocation Override geolocation.
app_set_locale Override locale.
app_set_timezone Override timezone.
app_set_color_scheme Override prefers-color-scheme.
app_network_emulate CDP Network.emulateNetworkConditions: offline / slow-3g / fast-3g / custom.

Codegen export (3)

Tool Purpose
app_codegen_start Begin recording every tool call with its args.
app_codegen_stop Stop recording.
app_codegen_export Output as runnable Playwright .spec.ts or replayable MCP transcript.

Recipes

ElectroMCP ships four recipe packages — each one attaches to your real installed Electron app and exposes a small, scoped, secure tool surface:

  • @electromcp/recipes-slackslack_read_channel, slack_send_message, slack_search, slack_react
  • @electromcp/recipes-discorddiscord_read_channel, discord_send, discord_dm
  • @electromcp/recipes-notionnotion_search, notion_create_page, notion_update_db_row, notion_export_db
  • @electromcp/recipes-linearlinear_create_issue, linear_comment, linear_move_issue, linear_triage_inbox

Security model (every recipe inherits)

  • auth_scope allowlist — explicit list of channels / workspaces / accounts the agent may touch. Default empty → no access until granted.
  • PII redaction by default — email / phone / credit-card patterns are scrubbed from returned text unless pii: 'allow' is set per-call.
  • Append-only audit log at ~/.electromcp/audit.log — every recipe call writes { timestamp, tool, redacted args, return summary }. You can review what the agent saw and sent.
  • Read-only by defaultmode: 'readonly' ships on every recipe. Write actions (*_send_*, *_post_*, *_create_*) require explicit mode: 'write'.
  • Token-leak assertion — the test suite asserts no recipe tool ever returns a cookie / bearer / OAuth token. CI fails on regression.

Selectors are pinned per app version via @electromcp/selector-tools (capture, pin, validate). When the target app upgrades, the recipe loads the closest matching selector file and warns on fallback. Auto-PR'd selector regen is aspirational; manual capture is the v2.0 path.


Migration


Recommended flow

1. app_health                 confirm connection
2. ui_accessibility_snapshot  cheap structure
3. ui_screenshot              only if visuals matter
4. ui_click / ui_fill / ...   interact
5. electron_main_state        verify main-side state

For debugging:

1. app_console_logs           renderer output
2. app_evaluate_renderer      live DOM / variables
3. electron_ipc_traffic       what crossed the bridge

Environment variables

Variable Default Description
ALLOW_MAIN_EVAL unset Set to 1 to enable app_evaluate_main.

Limitations

Surfaced honestly so you know what you're buying. Several of these are tracked for v2.1.

  • PII redaction is recipe-side only. Core tools (ui_accessibility_snapshot, electron_main_state, electron_ipc_traffic, etc.) return raw text and state. If your app's accessibility tree, IPC traffic, or main-state contains emails / phone numbers / tokens / credit-card patterns, those will surface unredacted in tool returns. Recipe packages (@electromcp/recipes-*) auto-redact via the recipe template. For core tools, redact in your AI prompt or call redactObject from electromcp/security in user-side post-processing.
  • electron_ipc_send sends from the renderer. If your app uses contextIsolation: true without a contextBridge-exposed ipcRenderer and without nodeIntegration: true, the call fails with a clear error.
  • electron_ipc_listen is not "everything that crosses the bridge". Renderer → main ipcRenderer.send traffic is only observable when the app actually registered an ipcMain.on handler for that channel; sends to channels with no handler are silently dropped by Electron itself and we cannot see them. Main → renderer webContents.send is not observed at all in v2.0 — Electron offers no public hook in the main process to intercept outgoing sends without monkey-patching every webContents instance. Tracked for v2.1.
  • app_update_assert_called blockmap is not real electron-builder wire format. The fake update server emits an uncompressed JSON description of block hashes, not the actual .blockmap binary format. Strict real electron-updater clients will reject it. The harness is sufficient for testing your app's update event handlers; it is not a full blockmap fuzzer. Tracked for v2.1.
  • app_deeplink_assert requires fixture cooperation. It reads from globalThis.__deeplink__.events which the target app must populate (typically via a small instrumentation snippet at boot). The cold-start variant is fully turnkey; warm-start assertions need the snippet. Tracked for v2.1.
  • app_screenshot_match_docker does not run Electron in Docker. It screenshots an HTTP URL inside a pinned Playwright Chromium container so baselines don't drift across host OS / GPU / fonts. Useful for comparing renderer output against a normalized reference; does not exercise main-process or native-shell behavior. The host-Electron path (ui_screenshot_match) is what you want for full-app regression. Tracked for v2.1 (Electron-in-Docker is non-trivial because Electron needs root + display + sandbox bypass).
  • partition_snapshot is partial. Partitions not bound to an open BrowserWindow are invisible (Electron has no public partition-enum API). localStorage is only walked for partitions with at least one open window. IndexedDB returns aggregate usage, not per-DB enumeration. serviceWorkers is [] on Electron <28. The exported helpers' JSDoc spells this out per-method.
  • HAR / video recording require launch mode (cannot be added to an existing CDP session).
  • app_evaluate_main is launch mode only. It requires either ALLOW_MAIN_EVAL=1 in the environment OR a preset that grants it (preset: 'permissive' | 'dev', or settings: { allowMainEval: true } on launch).
  • Recipe selectors rot. The 4 recipe packages pin selectors per app version. When the target app upgrades past your pinned version, loadSelectors warns at >30 days stale and escalates to "deprecated" at >90 days. The recipe packages' CONTRIBUTING.md documents the regen flow. CI auto-PR'd selector capture is aspirational for v2.0 — manual capture is the v2.0 path.
  • Headless is not officially supported by Electron. On Linux CI: xvfb-run npm test (ElectroMCP auto-detects + auto-launches xvfb when DISPLAY is unset).

Development

git clone https://github.com/TheRealSeanDonahoe/electromcp
cd electromcp
npm install
npm run build    # tsc -> build/
npm run dev      # tsx src/index.ts
npm test         # full suite

Contributing

Recipe selectors break weekly. PRs against packages/recipes-*/selectors.<version>.json auto-merge if CI is green. Each recipe has a CONTRIBUTING.md with the selector-regen flow.

License

MIT

Footnotes

  1. Core tools do not auto-enforce auth_scope / PII redaction / audit log / readonly mode. These primitives are exposed via the electromcp/security subpath export and are wired automatically into every recipe via @electromcp/recipes-template. To apply them to core tool outputs, redact in your AI prompt or call redactObject in user-side post-processing.

About

Connect Claude and any AI agent to Electron apps — click, test, screenshot, IPC, HAR. An Electron · Playwright · MCP Server.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors