Skip to content

Headless AI harness

The headless harness runs ProxyPro’s engine + MCP server without the desktop app, and gives the agent a real Playwright-driven browser routed through the proxy. An AI agent (Claude Code) can then drive a web app, watch the decrypted network traffic, and correlate which requests each UI action caused — the loop you need to develop and test a web app fast against mocked backends.

It’s the same MCP server as the in-app integration, plus three things the GUI doesn’t have:

  • a headless entrypoint (pnpm harness) — no window, scriptable, CI-friendly
  • browser toolsbrowser_goto, browser_click, browser_snapshot, …
  • action↔capture correlationflows_for_action, wait_for_idle

Prerequisites

Two ways to run: the published npx proxypro-harness CLI (no checkout — see Start the harness), or from a source checkout (below, for developing ProxyPro itself).

  1. Clone + install:

    Terminal window
    git clone https://github.com/trongitnlu/proxypro
    cd proxypro && make bootstrap # Go protoc plugins + pnpm deps
    make engine # build the proxypro-engine binary
  2. Install the Chromium the browser tools drive:

    Terminal window
    cd app && pnpm exec playwright install chromium

1. Start the harness

No checkout? Run the published CLI — it boots the same harness on plain Node:

Terminal window
npx proxypro-harness
# resolves the engine, installs Chromium on first run, then:
# [harness] MCP ready at http://127.0.0.1:9091/mcp — engine: running

proxypro-harness resolves proxypro-engine automatically: --engine <path>, PROXYPRO_ENGINE env var, installed ProxyPro.app (macOS, auto-detected), source build, or auto-downloaded on first run (macOS) from GitHub Releases and cached at ~/.proxypro/engine/<tag>/proxypro-engine.

The repo is private — the auto-download requires a GitHub token with repo read access. Set GITHUB_TOKEN (or GH_TOKEN), or run gh auth login once (the gh CLI token is picked up automatically). Without a token the download returns 404 with an actionable error message. Pass --engine or set PROXYPRO_ENGINE to skip the download entirely.

Flags: --headed (visible window), --record (save .webm replays), --no-auto-install (skip the Chromium download). See proxypro-harness --help.

From a source checkout (for development on ProxyPro itself):

Terminal window
cd app && pnpm harness
# [harness] engine running
# [harness] MCP ready at http://127.0.0.1:9091/mcp — engine: running

Either way this boots the Go engine (proxy on :9090) and the MCP HTTP server on :9091 — no desktop window. Leave it running; Ctrl-C shuts it down cleanly.

2. Connect Claude Code

Terminal window
claude mcp add --transport http proxypro http://127.0.0.1:9091/mcp
claude mcp list # proxypro: ... - ✓ Connected

Start a Claude Code session after adding the server (MCP servers are picked up at session start), then talk to it in plain language — see the loop below.

3. The agentic loop

The harness exposes the full in-app tool set plus these headless-only tools:

ToolWhat it does
browser_launchStart Chromium (proxied + TLS-ignore). Optional — other browser tools auto-launch. headless:false shows a live desktop window; record:true captures a .webm replay.
browser_gotoNavigate to a URL. Returns final URL + status + an actionId.
browser_click / browser_fillInteract by selector (CSS, text=, role=).
browser_snapshotCompact ARIA snapshot of the page (token-cheap — prefer over screenshots).
browser_screenshotPNG (base64). Use only when a visual is needed.
browser_evalEvaluate a JS expression in the page.
browser_closeEnd the session (engine stays up); returns the saved .webm replayPath when launched with record:true.
flows_for_actionWhich captured flows did action N trigger (marker → window).
wait_for_idleWait until an action stops producing new flows (settles async XHRs).

Every browser_* call returns an actionId; hand it to flows_for_action to see exactly which network requests that action caused. A typical develop-and-test session:

  1. See everything. Tell the agent to decrypt all HTTPS for the session:

    “Use ProxyPro: set SSL proxying to all.”

    (Calls set_ssl_proxying_mode("all") — see the gotcha below.)

  2. Mock the backend so you can build the frontend before the API exists:

    “Mock GET https://demo.test*/api/orders to return {orders:[…]} as JSON, and serve an HTML page at https://demo.test*/.”

    (Calls create_map_local_rule — note the * in the pattern, see URL patterns.)

  3. Drive the app that’s served entirely from your mocks:

    “Open https://demo.test/, click ‘Load orders’, then tell me which network calls that click triggered.”

    The agent chains browser_goto → browser_click → wait_for_idle → flows_for_action and reports the /api/orders call it caused.

  4. Inject a fault to test resilience — the thing plain browser automation can’t do without a broken backend:

    “Now make /api/orders return 500 and click again — does the UI handle it?”

    The agent updates the mock to 500, re-clicks, and reads the error state back from browser_snapshot.

  5. Clean up when done:

    “Delete the demo rules and set SSL proxying back to allowlist.”

Gotchas & tips

Watch it live, or save a replay

The browser is headless by default (unattended/CI). To watch the agent drive it in a real desktop window, have it launch with headless:false:

“Launch the browser with headless:false so I can watch, then open https://demo.test/.”

To review the run afterwards, launch with record:true — ProxyPro records the whole session to a .webm and returns its path when the agent calls browser_close:

“Launch with record:true, run the checkout flow, then close the browser and tell me where the replay was saved.”

Videos land in proxypro-replays/ (under the harness’s working directory); open the returned path in any video player. Recording finalizes on browser_close (or when the harness shuts down) — there’s no partial mid-session video.

Capture nothing by default?

ProxyPro ships in allowlist SSL mode — only explicitly allowed hosts are decrypted; everything else is tunnelled (loads fine, but isn’t captured). For an agent session where you want full visibility, switch to decrypt-all once:

set_ssl_proxying_mode("all")

…or add specific hosts with add_ssl_proxying_host. Remember this persists to the shared config — set it back to allowlist when you’re done.

Map Local patterns need a wildcard

urlPattern is an anchored, full-URL glob where * means zero-or-more of any character. Two consequences:

  • A bare host like https://demo.test/ must match the URL exactly — and MITM’d HTTPS URLs carry the port (https://demo.test:443/), so the bare form won’t match.

  • Use a * to absorb the port and any path:

    GoalPattern
    The site root onlyhttps://demo.test*/
    One endpointhttps://demo.test*/api/orders
    Everything under a hosthttps://demo.test*
    A path subtreehttps://api.example.com*/v1/*

Same-origin mocks avoid CORS

Mocking both the page and its API on the same host (https://demo.test*/ and https://demo.test*/api/…) keeps fetches same-origin — no CORS setup. If page and API are on different hosts, add an Access-Control-Allow-Origin response header in the Map Local rule (the engine then auto-handles OPTIONS preflight).

Prefer snapshots over screenshots

browser_snapshot returns a compact ARIA tree — usually enough to locate elements and read page state, at a fraction of a screenshot’s token cost. Reach for browser_screenshot only when you actually need pixels.

Cleanup

The harness shares the desktop app’s persisted rules.db, so anything the agent creates outlives the session:

  • Have the agent delete_rule(ruleId) each mock it created (it gets the id back from create_*).
  • Reset set_ssl_proxying_mode("allowlist") if you switched it.
  • Or open the desktop app’s Rules tab afterwards to review/remove leftovers.

Where to next