FlowProxy

Documentation

How to drive FlowProxy from an AI agent over MCP, from the terminal with the flowproxy CLI, or directly over its local REST API — plus the certificate setup that trips people up, and the limits worth knowing before you hit them.

Overview

FlowProxy captures HTTP and HTTPS traffic you route through it, and exposes what it captured three ways: to an AI agent over MCP, to your shell through the flowproxy CLI, and to anything else over a local REST API. All three talk to the same capture engine and see the same sessions.

Read this before following any command below

The Mac App Store build is sandboxed, so it ships the app on its own — no flowproxy command and no background daemon. If that is your build, the CLI sections do not apply to you. The MCP section is your scripting and automation story, and it is not a lesser one — it reaches every capability the CLI does.

Which build has what

Mac App StoreDirect download
App, HTTPS inspection, replayYesYes
Local API + MCP (for AI agents)YesYes
flowproxy command lineNoYes
One-click CA trustTrust it in Keychain AccessYes

The sandbox is the reason, not a tier: a sandboxed app cannot place a binary on your PATH or spawn a background daemon. Everything else is identical.

AI agents (MCP)

Both builds

FlowProxy hosts an MCP server so Claude Code, Codex, Claude Desktop, Cursor, or VS Code can read captured traffic and drive the proxy directly — structured JSON, not screenshots of a window. The transport is streamable HTTP at POST /mcp.

Off by default

Turn it on in Settings → Local API. Every call must present a bearer token, and the same switch revokes access immediately.

Tools

ToolDoes
list_sessionsEvery captured request, with the same filters as the CLI
get_sessionFull headers, body, and timing for one session
proxy_statusWhether the proxy is running, and on which port
clear_sessionsEmpty the capture buffer
start_proxyBring the proxy up
stop_proxyTake it down
replay_sessionRe-send with overridden method, headers, or body

Connecting your agent

Enable the local API in Settings, then use the built-in picker to copy the snippet for your client — it fills in the port and token for you. There is also a Copy instructions for agent button that emits a primer you can paste into any agent. The commands it generates look like this:

# Claude Code
claude mcp add --transport http flowproxy http://127.0.0.1:<port>/mcp \
  --header "Authorization: Bearer <token>"

# Codex
export FLOWPROXY_TOKEN=<token>
codex mcp add flowproxy --url http://127.0.0.1:<port>/mcp \
  --bearer-token-env-var FLOWPROXY_TOKEN

Codex will not accept a token inline — --bearer-token-env-var takes the name of an environment variable holding it, not the value. Claude Desktop and Cursor use an mcpServers JSON block; VS Code uses the same shape under servers.

Certificates and browser trust

Both builds

To read inside HTTPS connections, FlowProxy generates a certificate authority on your Mac and issues per-host certificates signed by it. Your client has to trust that CA. The direct download build can install and trust it for you; in the App Store build you trust it yourself in Keychain Access.

Firefox needs a second step — and fails silently without it

Importing the CA into Firefox is not enough.

The import dialog's “Trust this CA to identify websites” checkbox is unchecked by default. Leave it unchecked and the certificate is imported and trusted for nothing — which looks identical to a correct install unless you know to check the trust column.

The symptom is Firefox reporting MOZILLA_PKIX_ERROR_MITM_DETECTED — “Software is Preventing Firefox From Safely Connecting to This Site” — which reads like a fault in the proxy but is not one.

The quickest way to confirm it is Firefox and not FlowProxy: if this works while Firefox does not, the proxy is fine and the trust setting is not. curl uses the macOS keychain; Firefox keeps its own certificate store.

curl -x http://127.0.0.1:8888 https://example.com

Either fix works:

  • Settings → Privacy & Security → Certificates → View Certificates → Authorities, select FlowProxy CA, Edit Trust, tick “This certificate can identify websites”, then restart Firefox.
  • Or set security.enterprise_roots.enabled to true in about:config, which makes Firefox honour the macOS keychain, then restart Firefox.

REST API

Both builds

The API binds 127.0.0.1 and takes the first free port in 7878–7899. The background daemon writes its port to ~/.flowproxy/service.port; the app shows its port in Settings. In the App Store build the app hosts the API itself — there is no daemon, and none is needed.

Every endpoint requires a bearer token. Without one you get 401.

Loopback is not authentication. Any process on your machine can reach the port, and responses contain whole captured requests — cookies, API keys and all. The token lives at ~/.flowproxy/token (mode 0600), is generated on first run, and is shared by the CLI and the daemon. In the App Store build that path is inside the app container, so read the value from Settings instead.

curl -H "Authorization: Bearer $(cat ~/.flowproxy/token)" \
  "http://127.0.0.1:7878/sessions?limit=10"

The CLI reads the token itself, so CLI examples need no header.

EndpointNotes
GET /proxy/statusWhether the proxy is running, and on which port
POST /proxy/startBody {"port":N}
POST /proxy/stop—
GET /sessionsQuery ?limit= &host= &method= &status= &search=
GET /sessions/:idFull request and response detail
DELETE /sessionsClear the capture buffer
POST /sessions/:id/replayBody {"method":…,"headers":{…},"body":"…"}
GET /reverse-proxyList rules
POST /reverse-proxyCreate a rule
DELETE /reverse-proxy/:idRemove a rule
POST /mcpMCP streamable HTTP endpoint
POST /shutdownDaemon only — refused when the app hosts the API

Error codes

Both builds

Errors come back as a structured JSON body. These are what the REST API returns to any caller, so agents over MCP and scripts using curl see them too.

{
  "error": {
    "code": "not_found",
    "message": "Session abc123 not found"
  }
}
CodeHTTPMessage
unauthorized401“Missing or invalid bearer token. FlowProxy shows the token in Settings.”
not_found404Covers three different things: an unknown session ID, an unknown reverse proxy rule ID, and an unrecognised endpoint — “Session <id> not found”, “Rule <id> not found”, or “Unknown endpoint: <method> <path>”. A 404 here does not necessarily mean the session ID was wrong.
not_supported400“This API is hosted by the FlowProxy app. Turn it off with the Local API switch in Settings.” Returned by POST /shutdown, which only the daemon accepts.
start_failed500No fixed message — it passes through whatever the underlying failure was. Most often the listen port is already in use.
invalid_body400“Required: name (string), localPort (int), upstreamURL (string)”
invalid_id400“Invalid UUID: <id>”
invalid_url400“Session URL is invalid”
bad_request400“Invalid HTTP request”

The CLI does not print the code.

It parses the JSON error body and shows only the third column above — the message — alongside the HTTP status, and exits non-zero. The code strings are for callers hitting the API directly.

CLI reference

Direct download build only

The flowproxy command ships with the direct download build only. It talks to the background service over the same REST API documented above, reading the token for you. The service starts on demand — you do not need to manage it.

flowproxy <resource> <action> [options]

Every command takes --json for machine-readable output with stable field names. Use it whenever you are scripting.

Start the proxy

flowproxy proxy start
flowproxy proxy start --port 8080

Starts the HTTP/HTTPS proxy. Defaults to port 8888. HTTPS interception is always on when the CA is available — there is no flag for it.

FlagDescription
--port NListen on a specific port (default: 8888)
--jsonOutput machine-readable JSON
# JSON output
{ "port": 8888, "running": true }

Stop the proxy

flowproxy proxy stop

Stops the proxy but leaves the service running. Sessions are preserved.

Check status

flowproxy proxy status
flowproxy proxy status --json
# JSON output
{ "running": true, "port": 8888 }

Sessions

Direct download build only

Sessions are HTTP/HTTPS requests captured by the proxy. They are created automatically as traffic passes through.

List sessions

flowproxy sessions list
flowproxy sessions list --limit 20
flowproxy sessions list --host api.example.com
flowproxy sessions list --method POST
flowproxy sessions list --status 404
flowproxy sessions list --search "login"
flowproxy sessions list --host api.example.com --method POST --limit 5 --json
FlagDescription
--limit NReturn the N most recent matching sessions
--host HFilter by hostname (partial match)
--method MFilter by HTTP method (GET, POST, etc.)
--status SFilter by HTTP status code
--search QFull-text search across host, path, and method

All filters can be combined.

Get full session detail

flowproxy sessions get B5E91BA5-1234-5678-90AB-CDEF01234567 --json

Returns full request and response data including headers and bodies. Always use the full UUID from sessions list --json.

FieldTypeDescription
idstring (UUID)Unique session ID
methodstringHTTP method
hoststringHostname (no port)
pathstringURL path including query string
urlstringFull URL
protocolstring"HTTP" or "HTTPS"
statusint or nullHTTP response status code. null if request failed
duration_msint or nullRound-trip time in milliseconds
statestring"Pending", "Complete", or "Error"
timestampstringISO 8601 UTC timestamp
requestHeadersobjectRequest headers (detail view only)
responseHeadersobjectResponse headers (detail view only)
requestBodystring or nullRequest body as UTF-8 (detail view only)
responseBodystring or nullResponse body as UTF-8 (detail view only)

Clear sessions

flowproxy sessions clear

Removes all captured sessions from memory.

Replay

Direct download build only

Resend a captured session, optionally with overrides. The replayed request is stored as a new session and its full detail is returned.

# Exact repeat
flowproxy replay B5E91BA5-1234-5678-90AB-CDEF01234567 --json

# With overrides
flowproxy replay <id> --method POST --json
flowproxy replay <id> --header "Authorization: Bearer newtoken123" --json
flowproxy replay <id> --body '{"key":"value"}' --json
flowproxy replay <id> --method POST --header "Content-Type: application/json" --body '{"x":1}' --json
FlagDescription
--method MOverride the HTTP method
--header K:VAdd or override a single header (repeatable)
--body BOverride the request body (raw string)

Reverse proxy

Direct download build only

Traffic through a reverse proxy rule is not recorded.

It is forwarded correctly, but it does not appear in the session list. Use the forward proxy if you need to inspect it.

Reverse proxy rules forward traffic from a local port to an upstream URL. Rules are persisted to ~/.flowproxy/reverse_proxy_rules.json and restored on restart.

List rules

flowproxy reverse-proxy list --json

Add a rule

flowproxy reverse-proxy add --name "staging-api" --local-port 9001 --upstream https://staging.api.example.com --json

After adding, traffic to localhost:9001 is forwarded to the upstream.

FlagDescription
--name NDisplay name for the rule (required)
--local-port NLocal port to listen on (required)
--upstream URLUpstream URL to forward to (required)

Remove a rule

flowproxy reverse-proxy remove 6CC22EB2-1234-5678-90AB-CDEF01234567 --json

JSON mode

Direct download build only

Add --json to any command to get structured output. JSON output is always valid, fields are stable across versions, and errors are also structured.

flowproxy sessions list --json
flowproxy proxy status --json

Agent workflows

Direct download build only

Key rules when driving FlowProxy from a script or from an agent using the CLI. Agents connected over MCP get structured results already and can skip the JSON advice.

  • Always use --json — never parse human-readable output.
  • Check proxy status before acting; use full UUIDs from sessions list --json.
  • state: "Pending" sessions are still in-flight — re-fetch before inspecting.
  • status: null means no response received; check the error field.
  • Sessions are in-memory — they reset when the app quits.

Observe traffic from a running app

# 1. Start proxy
flowproxy proxy start --json

# 2. Configure your app to proxy through 127.0.0.1:8888

# 3. Inspect captured traffic
flowproxy sessions list --limit 20 --json
flowproxy sessions get <id> --json

# 4. Stop when done
flowproxy proxy stop --json

Find and replay a failing request

# Find 5xx errors
flowproxy sessions list --status 500 --json

# Replay the failing request
flowproxy replay <id> --json

# Replay with a fixed auth token
flowproxy replay <id> --header "Authorization: Bearer newtoken" --json

Quick reference

# Proxy lifecycle
flowproxy proxy status --json
flowproxy proxy start [--port N] --json
flowproxy proxy stop --json

# Sessions
flowproxy sessions list [--limit N] [--host H] [--method M] [--status S] [--search Q] --json
flowproxy sessions get <uuid> --json
flowproxy sessions clear --json

# Replay
flowproxy replay <uuid> [--method M] [--header K:V] [--body B] --json

# Reverse proxy
flowproxy reverse-proxy list --json
flowproxy reverse-proxy add --name N --local-port N --upstream URL --json
flowproxy reverse-proxy remove <uuid> --json

Troubleshooting

The browser shows failures and the session list is empty

A TLS handshake that never completes is recorded as an error session explaining why — so check the list before assuming nothing was captured. Traffic from a client that is not configured to use the proxy never reaches FlowProxy at all, and will not appear. If Firefox is the client, read the trust step first.

A request failed and the status code does not explain why

A 502 from FlowProxy means the upstream request failed, and nothing more specific than that. The actual reason — a DNS failure, a refused connection, a TLS error — is only in the session's error field. Read that before drawing a conclusion from the status: a host that no longer resolves and a genuinely broken upstream produce the same 502.

A session says “tunnelled without inspection”

No certificate could be produced for that host, so the connection passed through unread. The request still succeeded — you just cannot see inside it.

A site refuses to load through the proxy at all

Certificate-pinned sites reject any debugging proxy by design and cannot be inspected. That is the pinning working correctly, not a fault in FlowProxy.

Limits

  • Sessions are held in memory, capped at 50,000, oldest evicted first. They do not survive quitting the app.
  • Response bodies over 8 KB spill to a scratch file inside the app's container once 256 MB is resident, and reload transparently when you select the session.
  • WebSocket connections cannot be inspected. Upgrade and Connection are hop-by-hop headers and are stripped before forwarding, so the request reaches the server as an ordinary GET and the client gets the server's normal response instead of 101 Switching Protocols. The socket never establishes — and note there is no error to look for: you will see an ordinary 200 or 404.
  • The proxy binds 127.0.0.1 only. To capture from a phone or a VM, enable “Allow connections from other devices” in Settings and restart the proxy. It is off by default, and while it is on, anything that can reach your Mac on the network can route traffic through it.