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 tools —
browser_goto,browser_click,browser_snapshot, … - action↔capture correlation —
flows_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).
-
Clone + install:
Terminal window git clone https://github.com/trongitnlu/proxyprocd proxypro && make bootstrap # Go protoc plugins + pnpm depsmake engine # build the proxypro-engine binary -
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:
npx proxypro-harness# resolves the engine, installs Chromium on first run, then:# [harness] MCP ready at http://127.0.0.1:9091/mcp — engine: runningproxypro-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):
cd app && pnpm harness# [harness] engine running# [harness] MCP ready at http://127.0.0.1:9091/mcp — engine: runningEither 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
claude mcp add --transport http proxypro http://127.0.0.1:9091/mcpclaude mcp list # proxypro: ... - ✓ ConnectedStart 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:
| Tool | What it does |
|---|---|
browser_launch | Start Chromium (proxied + TLS-ignore). Optional — other browser tools auto-launch. headless:false shows a live desktop window; record:true captures a .webm replay. |
browser_goto | Navigate to a URL. Returns final URL + status + an actionId. |
browser_click / browser_fill | Interact by selector (CSS, text=, role=). |
browser_snapshot | Compact ARIA snapshot of the page (token-cheap — prefer over screenshots). |
browser_screenshot | PNG (base64). Use only when a visual is needed. |
browser_eval | Evaluate a JS expression in the page. |
browser_close | End the session (engine stays up); returns the saved .webm replayPath when launched with record:true. |
flows_for_action | Which captured flows did action N trigger (marker → window). |
wait_for_idle | Wait 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:
-
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.) -
Mock the backend so you can build the frontend before the API exists:
“Mock
GET https://demo.test*/api/ordersto return{orders:[…]}as JSON, and serve an HTML page athttps://demo.test*/.”(Calls
create_map_local_rule— note the*in the pattern, see URL patterns.) -
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_actionand reports the/api/orderscall it caused. -
Inject a fault to test resilience — the thing plain browser automation can’t do without a broken backend:
“Now make
/api/ordersreturn 500 and click again — does the UI handle it?”The agent updates the mock to
500, re-clicks, and reads the error state back frombrowser_snapshot. -
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:falseso I can watch, then openhttps://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:Goal Pattern The site root only https://demo.test*/One endpoint https://demo.test*/api/ordersEverything under a host https://demo.test*A path subtree https://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 fromcreate_*). - Reset
set_ssl_proxying_mode("allowlist")if you switched it. - Or open the desktop app’s Rules tab afterwards to review/remove leftovers.