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 Store | Direct download | |
|---|---|---|
| App, HTTPS inspection, replay | Yes | Yes |
| Local API + MCP (for AI agents) | Yes | Yes |
flowproxy command line | No | Yes |
| One-click CA trust | Trust it in Keychain Access | Yes |
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 buildsFlowProxy 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
| Tool | Does |
|---|---|
list_sessions | Every captured request, with the same filters as the CLI |
get_session | Full headers, body, and timing for one session |
proxy_status | Whether the proxy is running, and on which port |
clear_sessions | Empty the capture buffer |
start_proxy | Bring the proxy up |
stop_proxy | Take it down |
replay_session | Re-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_TOKENCodex 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 buildsTo 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.comEither 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.enabledtotrueinabout:config, which makes Firefox honour the macOS keychain, then restart Firefox.
REST API
Both buildsThe 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.
| Endpoint | Notes |
|---|---|
GET /proxy/status | Whether the proxy is running, and on which port |
POST /proxy/start | Body {"port":N} |
POST /proxy/stop | — |
GET /sessions | Query ?limit= &host= &method= &status= &search= |
GET /sessions/:id | Full request and response detail |
DELETE /sessions | Clear the capture buffer |
POST /sessions/:id/replay | Body {"method":…,"headers":{…},"body":"…"} |
GET /reverse-proxy | List rules |
POST /reverse-proxy | Create a rule |
DELETE /reverse-proxy/:id | Remove a rule |
POST /mcp | MCP streamable HTTP endpoint |
POST /shutdown | Daemon only — refused when the app hosts the API |
Error codes
Both buildsErrors 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"
}
}| Code | HTTP | Message |
|---|---|---|
unauthorized | 401 | “Missing or invalid bearer token. FlowProxy shows the token in Settings.” |
not_found | 404 | Covers 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_supported | 400 | “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_failed | 500 | No fixed message — it passes through whatever the underlying failure was. Most often the listen port is already in use. |
invalid_body | 400 | “Required: name (string), localPort (int), upstreamURL (string)” |
invalid_id | 400 | “Invalid UUID: <id>” |
invalid_url | 400 | “Session URL is invalid” |
bad_request | 400 | “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 onlyThe 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 8080Starts the HTTP/HTTPS proxy. Defaults to port 8888. HTTPS interception is always on when the CA is available — there is no flag for it.
| Flag | Description |
|---|---|
--port N | Listen on a specific port (default: 8888) |
--json | Output machine-readable JSON |
# JSON output
{ "port": 8888, "running": true }Stop the proxy
flowproxy proxy stopStops 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 onlySessions 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| Flag | Description |
|---|---|
--limit N | Return the N most recent matching sessions |
--host H | Filter by hostname (partial match) |
--method M | Filter by HTTP method (GET, POST, etc.) |
--status S | Filter by HTTP status code |
--search Q | Full-text search across host, path, and method |
All filters can be combined.
Get full session detail
flowproxy sessions get B5E91BA5-1234-5678-90AB-CDEF01234567 --jsonReturns full request and response data including headers and bodies. Always use the full UUID from sessions list --json.
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique session ID |
method | string | HTTP method |
host | string | Hostname (no port) |
path | string | URL path including query string |
url | string | Full URL |
protocol | string | "HTTP" or "HTTPS" |
status | int or null | HTTP response status code. null if request failed |
duration_ms | int or null | Round-trip time in milliseconds |
state | string | "Pending", "Complete", or "Error" |
timestamp | string | ISO 8601 UTC timestamp |
requestHeaders | object | Request headers (detail view only) |
responseHeaders | object | Response headers (detail view only) |
requestBody | string or null | Request body as UTF-8 (detail view only) |
responseBody | string or null | Response body as UTF-8 (detail view only) |
Clear sessions
flowproxy sessions clearRemoves all captured sessions from memory.
Replay
Direct download build onlyResend 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| Flag | Description |
|---|---|
--method M | Override the HTTP method |
--header K:V | Add or override a single header (repeatable) |
--body B | Override the request body (raw string) |
Reverse proxy
Direct download build onlyTraffic 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 --jsonAdd a rule
flowproxy reverse-proxy add --name "staging-api" --local-port 9001 --upstream https://staging.api.example.com --jsonAfter adding, traffic to localhost:9001 is forwarded to the upstream.
| Flag | Description |
|---|---|
--name N | Display name for the rule (required) |
--local-port N | Local port to listen on (required) |
--upstream URL | Upstream URL to forward to (required) |
Remove a rule
flowproxy reverse-proxy remove 6CC22EB2-1234-5678-90AB-CDEF01234567 --jsonJSON mode
Direct download build onlyAdd --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 --jsonAgent workflows
Direct download build onlyKey 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 statusbefore acting; use full UUIDs fromsessions list --json. state: "Pending"sessions are still in-flight — re-fetch before inspecting.status: nullmeans no response received; check theerrorfield.- 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 --jsonFind 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" --jsonQuick 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> --jsonTroubleshooting
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.
UpgradeandConnectionare 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 of101 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.1only. 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.