Skip to main content

Moonlighting

Invisible hands for your AI agent. A real Chrome, on its own hidden Windows desktop, driven by element references instead of coordinates — with credentials that never reach the model.

English · 中文

┌─ your desktop ─────────────────────────────┐
│  Hermes / Claude / Codex / any MCP client   │
└───────────────┬────────────────────────────┘
                │ MCP (stdio)  or  local HTTP
┌───────────────▼────────────────────────────┐
│  hidden desktop "HermesDesk"                │
│  ┌──────────────────────────────────────┐   │
│  │ real Chrome + persistent profile     │   │
│  │ (logins, wallet, extensions)         │   │
│  └──────────────────────────────────────┘   │
│  credential broker (DPAPI, origin-bound)    │
└─────────────────────────────────────────────┘

You never see a window flash. The agent never sees a password.

Why

Driving a browser as an agent usually goes wrong in three places:

Problem What most setups do What this does
Finding elements screenshot + guess coordinates one accessibility snapshot, act on [ref=e42]
Losing state fresh profile every run persistent profile on a private desktop; reopen and the session is still there
Handling passwords paste into the prompt user types into a masked popup → DPAPI-encrypted locally → injected by the broker, never returned to the model

Install

Requires Windows 10/11, Python 3.10+, Node 18+ (for the Playwright MCP child), Chrome.

Option A — pip (no paths to edit)

pip install moonlighting
moonlighting-setup --options          # where should browser data live?
moonlighting-setup --home "D:\moonlighting"
moonlighting                          # starts stream + broker + driver

Then wire it into your MCP client with no absolute paths at all:

{ "mcpServers": { "browser": { "command": "moonlighting-mcp" } } }

moonlighting-mcp, moonlighting-setup, moonlighting-session and moonlighting are console scripts installed by the package.

Option B — clone

git clone https://github.com/xinyuzjj/moonlighting.git
cd moonlighting
pip install -r requirements.txt

python scripts/setup.py --options          # where should browser data live?
python scripts/setup.py --home "D:\moonlighting"
python scripts/svc.py                      # starts stream + broker + driver

Either way, install_mcp.py writes the right config for you — it detects whether moonlighting-mcp is installed and falls back to absolute script paths if not:

python scripts/install_mcp.py                 # print configs with real paths
python scripts/install_mcp.py --write claude  # write it (backs up the original)
python scripts/install_mcp.py --write codex
python scripts/install_mcp.py --write all

svc.py / moonlighting prints a preview URL. Open it in the Hermes preview pane (or any browser) to watch and even click into the hidden desktop:

preview: http://127.0.0.1:8765/ctl

First run creates <home>/token.txt — every state-changing endpoint requires it.

Use it from an agent

Any MCP client (Claude Desktop, Claude Code, Codex CLI, Hermes)

scripts/mcp_server.py is a standard MCP stdio server exposing 27 tools (25 browser_* plus cred_list / cred_fill).

If you installed with pip, the whole config is one line — no paths:

{ "mcpServers": { "browser": { "command": "moonlighting-mcp" } } }

Cloned instead? Let install_mcp.py fill in the real paths:

python scripts/install_mcp.py --write all

Or write it by hand (Claude Desktop / Claude Code, mcpServers):

{
  "mcpServers": {
    "browser": {
      "command": "python",
      "args": ["C:\\path\\to\\moonlighting\\scripts\\mcp_server.py",
               "--cdp-endpoint", "http://127.0.0.1:18763"]
    }
  }
}

Codex CLI (~/.codex/config.toml — TOML, snake_case, and give it more startup time):

[mcp_servers.browser]
command = "python"
args = ["C:\\path\\to\\moonlighting\\scripts\\mcp_server.py", "--cdp-endpoint", "http://127.0.0.1:18763"]
startup_timeout_sec = 60
tool_timeout_sec = 120

Plain HTTP

TOKEN=$(python scripts/auth.py --print | sed 's/^token: //')

curl -H "X-Hermes-Token: $TOKEN" http://127.0.0.1:8897/snap        # whole page + refs
curl -H "X-Hermes-Token: $TOKEN" "http://127.0.0.1:8897/click?target=e42&element=Sign%20in"
curl -H "X-Hermes-Token: $TOKEN" "http://127.0.0.1:8899/list"      # saved credentials (no secrets)

Multiple agents, no interference

Each session gets its own desktop, profile, and ports — two agents cannot fight over tabs or invalidate each other's element refs.

python scripts/session.py new --name a1 --url https://example.com
python scripts/session.py list
python scripts/session.py stop --name a1
a1   cdp=18764  stream=8766  driver=8898  UP  http://127.0.0.1:8766/ctl

Security model

  • Passwords never enter the model context. The user types into a masked popup; the broker encrypts with Windows DPAPI, binds the secret to the page origin, and injects it. Tool results only contain {ok, verified, submitted, id_len, pwd_len}.
  • Origin-bound. A credential saved for https://github.com is refused on any other origin.
  • Token-gated. token.txt is generated on first run (mode 600). Read-only probes (/health, /status, /snapshot) stay open so the preview keeps working.
  • Audited. Every injection appends one line to <home>/audit.jsonl: timestamp, action, origin, handle, result code. No plaintext, ever.
  • No CAPTCHA evasion. This drives a real browser; it does not spoof fingerprints, solve puzzles, or defeat anti-bot systems.

Layout

scripts/
  mcp_server.py      standard MCP stdio server (browser_* + cred_*)
  browser_daemon.py  persistent driver: snapshot / click / type / tabs / eval
  desk_stream.py     live view of the hidden desktop (CDP screencast) + click/type back-channel
  vault_broker.py    credential broker: DPAPI, origin check, selector fill with read-back verify
  cred_dialog.py     masked popup the user types into
  login_flow.py      full login loop: popup → fill → verify → submit → retry on failure
  desk_chrome.py     launch / close Chrome on a named private desktop
  private_desktop.py Win32 desktop primitives (CreateDesktopW, WM_SETTEXT, PrintWindow)
  session.py         per-agent isolation (desktop + profile + ports)
  svc.py             start/stop the three services silently (no console windows)
  ports.py           port allocation: if the default is taken, pick a free one
  auth.py            token file + header helper
templates/           preview pages (/ctl interactive, /view read-only)
docs/SKILL.md        the long-form playbook: every pitfall we hit, with measurements

Platform

Windows only, by design: private desktops (CreateDesktopW), DPAPI, and silent process launch are Win32 primitives. The browser-control layer itself is portable — porting means replacing private_desktop.py and the DPAPI calls.

Acknowledgements

Standing on these projects:

Project How it is used
microsoft/playwright-mcp The accessibility-snapshot engine. mcp_server.py proxies browser_* tools to it; its [ref=...] model is what makes coordinate-free clicking possible. (Apache-2.0)
microsoft/playwright The browser driver underneath playwright-mcp. (Apache-2.0)
python-websockets/websockets CDP transport for the driver, the stream and the broker.
python-pillow/Pillow JPEG handling for the live preview and the drawn cursor.

Design ideas borrowed, no code vendored:

  • browserbase/stagehand — observe() returning real selectors instead of pixels; that is why this project resolves elements by selector or ref and never by coordinates.
  • browser-use/browser-use — the agent loop shape: snapshot, act, verify.
  • web-infra-dev/midscene — the vision-based fallback idea for canvas-only UIs (deliberately not enabled by default; it is slower).

License

MIT. The Apache-2.0 licenses of playwright-mcp and Playwright apply to those components.

Metadata

Release files for moonlighting 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for moonlighting 0.1.0
File Size Uploaded
moonlighting-0.1.0.tar.gz 64.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for moonlighting 0.1.0
File Interpreter ABI Platform
moonlighting-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 142.5 kB

Release files / moonlighting-0.1.0.tar.gz

Download URL moonlighting-0.1.0.tar.gz
Size 64.9 kB
Tags Source
SHA-256 checksum
How to use checksums
3e910af0695f319bc0018a7fbf417dc98ee3e474488be228334385be99aa7228
BLAKE2b-256 checksum
How to use checksums
a2cfd3e661042eba7a9d9cd1f9944cf32f3de70d84c50e0f9dd75ef93a8f462b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / moonlighting-0.1.0-py3-none-any.whl

Download URL moonlighting-0.1.0-py3-none-any.whl
Size 77.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0ab529965207fe993ecefbc8fc8e6bf19e50ac8039bfabfdb7eb4b2938187db4
BLAKE2b-256 checksum
How to use checksums
15c3586c7ee303968a3c390aaae5a2f29d679150f962201a0919b597a0e4fde7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page