Electron Stagewrightdocs

Load, configure, and diagnose plugins

Use a plugin when the core Electron-driving workflow needs accessibility audits, visual baselines, traces and replay, IPC, network interception, virtual time, storage, native UI, or packaged-app validation. This how-to shows how to load one, grant its narrowest gate, and inspect what the server enabled.

The core never discovers plugins automatically. Installing a package is not enough: explicitly name every plugin with --plugin (or pass it to createServer).

Install and load a plugin

Install the core, its launch peers, and the plugin together. For example, load trace support through npx. First prime the exact package list in a terminal, so Electron's initial binary-download output cannot corrupt an MCP stdio session:

npx -y \
  --package @electron-stagewright/core@0.5.0 \
  --package @electron-stagewright/plugin-trace@0.3.0 \
  --package playwright@1.61.1 \
  --package electron@42.3.0 \
  electron-stagewright doctor --json --plugin trace

Then use the same packages with the plugin flag:

npx -y \
  --package @electron-stagewright/core@0.5.0 \
  --package @electron-stagewright/plugin-trace@0.3.0 \
  --package playwright@1.61.1 \
  --package electron@42.3.0 \
  electron-stagewright --plugin trace

For an MCP client, preserve the same package and flag order in its args:

[
  "-y",
  "--package",
  "@electron-stagewright/core@0.5.0",
  "--package",
  "@electron-stagewright/plugin-trace@0.3.0",
  "--package",
  "playwright@1.61.1",
  "--package",
  "electron@42.3.0",
  "electron-stagewright",
  "--plugin",
  "trace"
]

Repeat --plugin to load more than one. A plugin may also be a trusted local package or file path; the server imports only the values you specify and rejects a bad manifest before it exposes tools. Shipped first-party suffixes are stable aliases, so --plugin production resolves to @electron-stagewright/plugin-production; full scoped package names remain valid and third-party specifiers are never rewritten.

Run doctor with the same configuration before serving:

electron-stagewright doctor --json \
  --plugin network \
  --plugin-config network='{"redactHeaders":["x-api-key"],"redactBodies":true}' \
  --tool-profile testing \
  --operation-timeout-ms 120000

The server_config check imports the explicitly selected trusted plugin, validates its manifest and configuration, runs setup against an unconnected server, and immediately runs teardown. It reports plugin names and option states but never raw config values. A plugin's top-level and setup code therefore runs during this check under the same trust boundary as normal server startup.

From code, pass imported plugin objects directly:

import { createServer } from '@electron-stagewright/core'
import tracePlugin from '@electron-stagewright/plugin-trace'

const server = await createServer({ plugins: [tracePlugin] })

Configure safely

Each plugin owns a Zod config schema. The CLI receives JSON keyed by the plugin namespace:

electron-stagewright \
  --plugin @electron-stagewright/plugin-network \
  --plugin-config network='{"redactHeaders":["x-api-key"],"redactBodies":true}'

Visual baselines use two operator-owned absolute roots. Keep accepted baselines under version control and send diagnostic captures elsewhere:

electron-stagewright \
  --plugin @electron-stagewright/plugin-visual \
  --plugin-config visual='{"baselineDir":"/abs/project/visual-baselines","artifactsDir":"/abs/project/visual-artifacts","fontFingerprint":"linux-ci-fonts-v1"}'

The programmatic equivalent is pluginConfigs:

const server = await createServer({
  plugins: [visualPlugin],
  pluginConfigs: {
    visual: {
      baselineDir: '/abs/project/visual-baselines',
      artifactsDir: '/abs/project/visual-artifacts',
      fontFingerprint: 'linux-ci-fonts-v1',
    },
  },
})

Configuration is parsed once, cloned, and frozen for that server. Invalid values fail closed with PLUGIN_CONFIG_INVALID; the details include a Zod path such as $.maxRecords and name the exact --plugin-config <name>=<json> or pluginConfigs.<name> input to correct. A server with a partial plugin set never starts.

electron_plugins never returns raw operator configuration. A plugin explicitly allowlists every top-level field it considers safe. An absent field means only that it is not disclosed — never treat it as proof that the field is unset or non-sensitive.

Inspect actual availability

After at least one plugin loads, call electron_plugins before launching an app. It needs no session and returns a deterministic, per-server snapshot:

{
  "ok": true,
  "plugins": [
    {
      "name": "storage",
      "version": "…",
      "state": "enabled",
      "tools": [
        { "name": "storage_cookies", "state": "enabled" },
        {
          "name": "storage_local_get",
          "state": "disabled",
          "disabledReason": { "kind": "eval_policy_disabled", "target": "renderer" }
        }
      ],
      "errorCodes": ["storage.EVAL_REQUIRED", "storage.UNSUPPORTED"],
      "requirements": {
        "evalTargets": ["renderer"],
        "transportCapabilities": ["canAccessStorage", "supportsRendererEval"]
      },
      "effectiveConfig": { "redactValues": false, "revealValues": false }
    }
  ]
}

On a plugin-free server, electron_plugins itself is absent. This is intentional: the core keeps that manifest lean. Load the required plugin and restart, then inspect this tool again.

Grant the narrowest gate

Loading a plugin does not grant eval or transport powers. Start with no eval and opt into only the target a tool needs:

# IPC instrumentation needs main-process eval.
electron-stagewright --plugin @electron-stagewright/plugin-ipc --allow-eval=main

# Per-key Web Storage and IndexedDB need renderer eval.
electron-stagewright --plugin @electron-stagewright/plugin-storage --allow-eval=renderer
Plugin Primary gate or scope
@electron-stagewright/plugin-a11y supportsRendererEval + supportsSurfaceTargeting; fixed engine, no --allow-eval; audit one selected surface at a time.
@electron-stagewright/plugin-visual supportsRendererEval + supportsSurfaceTargeting; fixed BrowserWindow capture preparation, no --allow-eval; configure baseline and artifact roots before use.
@electron-stagewright/plugin-trace No eval; records, promotes, and replays sessions.
@electron-stagewright/plugin-production No running session; validates a packaged macOS .app.
@electron-stagewright/plugin-ipc --allow-eval=main and supportsMainEval.
@electron-stagewright/plugin-network canIntercept; no eval.
@electron-stagewright/plugin-clock canControlClock; no eval.
@electron-stagewright/plugin-storage Cookies/snapshots need canAccessStorage; per-key Web Storage and IndexedDB additionally need renderer eval and supportsRendererEval.
@electron-stagewright/plugin-native-ui canAccessNativeUI; no eval.

Read the security model before enabling eval or a plugin that reads sensitive app state. See TOOL-REFERENCE.md for full namespaced tool schemas.

Troubleshoot a missing tool

  1. If electron_plugins is absent, no plugin is loaded. Confirm the package is installed, add the explicit --plugin flag, and restart.
  2. If the plugin is listed but a tool is disabled, apply its disabledReason.target with --allow-eval=main or --allow-eval=renderer, then restart with that narrow grant.
  3. If an enabled tool returns *.UNSUPPORTED, call electron_info after launch or attach and choose a transport whose capability matrix satisfies the plugin requirement.
  4. If startup reports PLUGIN_CONFIG_INVALID, correct the reported field path in the named config input. Do not remove a security setting merely to make startup succeed.

For trace workflows, continue with Capture diagnostics. For trust boundaries, read the security model.