Skip to main content

Reverse Proxy

The reverse proxy is agnt's most powerful feature, enabling comprehensive frontend debugging through transparent HTTP interception and JavaScript instrumentation.

Overview

When you start a proxy:

  1. HTTP Proxy - All traffic is forwarded to your dev server
  2. Traffic Logging - Requests and responses are captured
  3. JS Injection - Diagnostic JavaScript is added to HTML pages
  4. WebSocket Server - Receives metrics from instrumented frontend
  5. Page Sessions - Groups requests by page view

Quick Start

// Start your dev server
run {script_name: "dev"}

// Create proxy in front of it
proxy {action: "start", id: "app", target_url: "http://localhost:3000"}

The proxy auto-assigns a stable port based on the target URL (check listen_addr in response). Everything works normally, but now you have:

  • Complete HTTP traffic logs
  • JavaScript error capture
  • Performance metrics
  • 50+ diagnostic primitives via window.__devtool

Proxy Management

Start a Proxy

proxy {action: "start", id: "myapp", target_url: "http://localhost:3000"}
{
"id": "myapp",
"status": "running",
"target_url": "http://localhost:3000",
"listen_addr": ":45849"
}

Port Selection

By default, the proxy assigns a stable port based on a hash of the target URL:

  • Same URL always gets the same port (consistent across restarts)
  • Different URLs get different ports (avoids conflicts)
  • Ports are in range 10000-60000 (avoids well-known and ephemeral ports)

Recommended: Let the proxy choose the port automatically. Only specify port if you need a specific port.

Request a specific port:

proxy {action: "start", id: "app", target_url: "http://localhost:3000", port: 9000}
{listen_addr: ":9000"}

If the requested port is busy, the proxy finds an available one:

proxy {action: "start", id: "app", target_url: "http://localhost:3000", port: 9000}
{
"listen_addr": ":45123",
"message": "Port 9000 was busy, using 45123"
}

Check Status

proxy {action: "status", id: "myapp"}
{
"id": "myapp",
"status": "running",
"uptime": "15m32s",
"total_requests": 1542,
"log_stats": {
"http_entries": 1000,
"error_entries": 3,
"performance_entries": 45
}
}

List All Proxies

proxy {action: "list"}
{
"proxies": [
{"id": "frontend", "status": "running", "listen_addr": ":8080"},
{"id": "api", "status": "running", "listen_addr": ":8081"}
],
"active_count": 2
}

Stop a Proxy

proxy {action: "stop", id: "myapp"}
{message: "Proxy stopped"}

Traffic Logging

Query HTTP Traffic

proxylog {proxy_id: "app", types: ["http"], limit: 20}
{
"entries": [
{
"type": "http",
"method": "GET",
"url": "/api/users",
"status": 200,
"duration_ms": 45,
"request_headers": {...},
"response_headers": {...},
"request_body": null,
"response_body": "[{\"id\": 1, ...}]"
}
]
}

Filter by Method

proxylog {proxy_id: "app", types: ["http"], methods: ["POST", "PUT"]}
→ Only POST and PUT requests

Filter by Status Code

proxylog {proxy_id: "app", types: ["http"], status_codes: [500, 502, 503]}
→ Only server errors

Filter by URL Pattern

proxylog {proxy_id: "app", types: ["http"], url_pattern: "/api"}
→ Only requests containing "/api"

Time-Based Queries

proxylog {proxy_id: "app", types: ["http"], since: "5m"}
→ Last 5 minutes

proxylog {proxy_id: "app", since: "2024-01-15T10:00:00Z", until: "2024-01-15T10:30:00Z"}
→ Specific time range

Error Tracking

The proxy automatically captures frontend JavaScript errors:

proxylog {proxy_id: "app", types: ["error"]}
{
"entries": [
{
"type": "error",
"message": "Cannot read property 'map' of undefined",
"source": "http://localhost:8080/static/js/main.js",
"line": 142,
"column": 23,
"stack": "TypeError: Cannot read property 'map' of undefined\n at UserList...",
"url": "http://localhost:8080/users",
"timestamp": "2024-01-15T10:32:15Z"
}
]
}

Captures:

  • Uncaught exceptions
  • Unhandled promise rejections
  • Error source and line/column
  • Full stack trace
  • Page URL where error occurred

Performance Metrics

Automatically collected on every page load:

proxylog {proxy_id: "app", types: ["performance"]}
{
"entries": [
{
"type": "performance",
"url": "http://localhost:8080/dashboard",
"navigation": {
"dom_content_loaded": 245,
"load_event": 892
},
"paint": {
"first_paint": 156,
"first_contentful_paint": 234
},
"resources": [
{"name": "main.js", "duration": 123, "size": 45678},
{"name": "styles.css", "duration": 45, "size": 12345}
]
}
]
}

Metrics include:

  • DOM content loaded time
  • Page load event time
  • First paint / First contentful paint
  • Resource timing (up to 50 resources)

Page Sessions

Group requests by page view for easier debugging:

currentpage {proxy_id: "app"}
{
"sessions": [
{
"id": "page-1",
"url": "http://localhost:8080/dashboard",
"started_at": "2024-01-15T10:30:00Z",
"resource_count": 24,
"error_count": 0
},
{
"id": "page-2",
"url": "http://localhost:8080/users",
"started_at": "2024-01-15T10:31:15Z",
"resource_count": 18,
"error_count": 2
}
]
}

Get details for a specific page:

currentpage {proxy_id: "app", action: "get", session_id: "page-2"}
{
"session": {
"id": "page-2",
"url": "http://localhost:8080/users",
"document": {...},
"resources": [
{"url": "/static/js/main.js", "status": 200, "duration": 123},
{"url": "/api/users", "status": 200, "duration": 456}
],
"errors": [
{"message": "Cannot read property 'map'...", "line": 142}
],
"performance": {...}
}
}

Executing Browser Code

Run JavaScript in connected browsers:

proxy {action: "exec", id: "app", code: "document.title"}
{result: "My App - Dashboard"}

proxy {action: "exec", id: "app", code: "window.location.href"}
{result: "http://localhost:8080/dashboard"}

This is how you access the Frontend Diagnostics API.

Floating Indicator

Every proxied page includes a floating indicator bug in the corner that provides quick access to devtool features:

Features

  • Message Panel - Type messages to send to your AI agent
  • Screenshot - Click and drag to select an area for screenshot
  • Element Selection - Click to select any element and capture its details
  • Sketch Mode - Draw wireframes directly on the page
  • Audit Dropdown - Quick access to quality audits

Audit Actions

The Audit dropdown provides one-click access to diagnostic functions, organized by category:

Quality Audits

ActionDescription
Full Page AuditComprehensive audit with A-F grade and recommendations
AccessibilityWCAG issues (missing alt, labels, contrast)
SecurityMixed content, XSS risks, missing noopener
SEO / MetaMeta tags, headings, document structure

Layout & Visual

ActionDescription
Layout IssuesOverflows, z-index contexts, offscreen elements
Text FragilityTruncation, overflow, font issues (WCAG 1.4.10)
Responsive RiskElements that may break at different viewport sizes

Debug Context

ActionDescription
Last Click ContextWhat the user just clicked + mouse trail
Recent DOM ChangesElements added/removed/modified in last 30 seconds

State & Network

ActionDescription
Browser StatelocalStorage, sessionStorage, cookies
Network/ResourcesResource timing and loading data

Technical

ActionDescription
DOM ComplexityNode count, depth, performance impact
CSS QualityInline styles, !important usage

When you run an audit, the results are:

  1. Executed immediately in the browser
  2. Summarized as a chip in the message area
  3. Logged to the proxy for the AI agent to query via proxylog

This lets the LLM receive structured diagnostic data instead of you describing problems in natural language.

Programmatic Control

// Toggle indicator visibility
window.__devtool.indicator.show()
window.__devtool.indicator.hide()
window.__devtool.indicator.toggle()

// Toggle the message panel
window.__devtool.indicator.togglePanel()

Log Types

Messages and attachments from the indicator are logged and queryable:

// User messages from the panel
proxylog {proxy_id: "app", types: ["panel_message"]}

// Screenshot captures
proxylog {proxy_id: "app", types: ["screenshot"]}

// Sketch captures
proxylog {proxy_id: "app", types: ["sketch"]}

WebSocket Support

The proxy transparently handles WebSocket connections:

  • Hot Module Replacement (HMR) works normally
  • WebSocket upgrades are proxied to the target
  • No additional configuration needed
Browser ←→ Proxy (8080) ←→ Dev Server (3000)

└── WebSocket for HMR

Log Statistics

proxylog {proxy_id: "app", action: "stats"}
{
"total_entries": 1542,
"by_type": {
"http": 1489,
"error": 8,
"performance": 45
},
"dropped": 0,
"max_entries": 1000
}

Note: The log is a circular buffer (default 1000 entries). When full, oldest entries are dropped.

Real-World Examples

Debugging API Issues

// Find failed API calls
proxylog {proxy_id: "app", types: ["http"], url_pattern: "/api", status_codes: [400, 401, 403, 404, 500]}
→ Found POST /api/users returning 400

// Get details
proxylog {proxy_id: "app", types: ["http"], url_pattern: "/api/users", methods: ["POST"]}
{
"request_body": "{\"email\": \"invalid\"}",
"response_body": "{\"error\": \"Invalid email format\"}"
}

Performance Investigation

// Find slow pages
proxylog {proxy_id: "app", types: ["performance"]}
→ Dashboard page: load_event = 3500ms

// Check resource loading
currentpage {proxy_id: "app", action: "get", session_id: "page-1"}
→ main.js took 2100ms to load (blocking)

Error Correlation

// See errors
proxylog {proxy_id: "app", types: ["error"]}
→ Error on /users page at line 142

// Check what API calls happened before error
currentpage {proxy_id: "app", action: "get", session_id: "page-2"}
→ GET /api/users returned 500 before the JavaScript error

Multiple Environments

// Proxy staging (each gets unique port based on URL hash)
proxy {action: "start", id: "staging", target_url: "https://staging.example.com"}

// Proxy production (read-only debugging)
proxy {action: "start", id: "prod", target_url: "https://example.com"}

// Compare behavior
proxylog {proxy_id: "staging", types: ["http"], url_pattern: "/api/users"}
proxylog {proxy_id: "prod", types: ["http"], url_pattern: "/api/users"}

Configuration

Log Size

Default: 1000 entries. Configure when starting:

proxy {action: "start", id: "app", target_url: "...", max_log_size: 5000}

Auto-Restart

Proxies automatically restart if the HTTP server crashes (max 5 restarts per minute). Check status for crash info:

proxy {action: "status", id: "app"}
{
"last_error": "bind: address already in use",
"restart_count": 2
}

Best Practices

  1. Use Meaningful IDs - frontend, api, staging not proxy1
  2. Check listen_addr - Port may be auto-assigned if busy
  3. Clear Old Sessions - currentpage {action: "clear"} periodically
  4. Monitor Dropped Logs - Check stats for dropped count
  5. Use Page Sessions - Easier than searching raw HTTP logs

Remote Debugging & Live Device Testing

One of the most powerful features of agnt's proxy is the ability to share your local development server with remote devices while maintaining full instrumentation. This enables:

  • Testing on real mobile devices (iOS, Android)
  • Cross-browser testing with BrowserStack or similar services
  • Sharing work-in-progress with teammates or stakeholders
  • Debugging issues that only occur on specific devices

Tunnel Provider Options

agnt provides built-in support for tunnel services, plus you can manually configure any tunnel provider.

Best for: Most use cases - free, fast, no account required.

// Start proxy with external access
proxy {action: "start", id: "app", target_url: "http://localhost:3000", bind_address: "0.0.0.0"}

// Start Cloudflare tunnel with auto-configuration
tunnel {action: "start", id: "app", provider: "cloudflare", local_port: 45849, proxy_id: "app"}

Pros:

  • Free, no account required
  • Fast and reliable
  • HTTPS by default
  • No bandwidth limits
  • Automatic URL discovery

Install:

# macOS
brew install cloudflare/cloudflare/cloudflared

# Linux (Debian/Ubuntu)
curl -L -o cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb

# Windows
winget install --id Cloudflare.cloudflared

ngrok

Best for: Stable URLs, webhook testing, request inspection dashboard.

tunnel {action: "start", id: "app", provider: "ngrok", local_port: 45849, proxy_id: "app"}

Pros:

  • Stable URLs (with paid plan)
  • Built-in request inspection dashboard
  • Webhook replay and integrations
  • Custom domains available

Install:

# macOS
brew install ngrok/ngrok/ngrok

# Linux/Windows - download from https://ngrok.com/download

# Configure (required)
ngrok config add-authtoken <your-token>

Tailscale Funnel (Manual Setup)

Best for: Teams already using Tailscale, persistent URLs, secure sharing.

Tailscale Funnel isn't natively integrated, but works well with agnt's proxy using manual configuration:

# 1. Start your proxy on all interfaces
proxy {action: "start", id: "app", target_url: "http://localhost:3000", bind_address: "0.0.0.0"}
# Note the port from listen_addr (e.g., 45849)

# 2. In a terminal, start Tailscale Funnel
tailscale funnel 45849
# This gives you a URL like: https://your-machine.tailnet-name.ts.net/

# 3. Update the proxy with the public URL for proper URL rewriting
proxy {action: "start", id: "app", target_url: "http://localhost:3000", bind_address: "0.0.0.0", public_url: "https://your-machine.tailnet-name.ts.net"}

Pros:

  • Persistent, memorable URLs (based on machine name)
  • Integrates with existing Tailscale setup
  • No third-party account needed if you have Tailscale
  • End-to-end encrypted

Requirements:

  • Tailscale installed and authenticated
  • Funnel enabled on your tailnet (requires admin approval)

Live Device Testing Workflow

Testing on Your Phone

// 1. Start your dev server
run {script_name: "dev"}

// 2. Start instrumented proxy on all interfaces
proxy {
action: "start",
id: "mobile",
target_url: "http://localhost:3000",
bind_address: "0.0.0.0"
}
// Response: {listen_addr: "0.0.0.0:45849"}

// 3. Start tunnel
tunnel {
action: "start",
id: "mobile",
provider: "cloudflare",
local_port: 45849,
proxy_id: "mobile"
}
// Response: {public_url: "https://random-words.trycloudflare.com"}

// 4. Open the URL on your phone - you now have:
// - Full error capture from mobile browser
// - Floating indicator to send messages to your AI agent
// - Screenshot capabilities
// - All __devtool diagnostics

Checking Mobile-Specific Errors

// After testing on device, check for errors
proxylog {proxy_id: "mobile", types: ["error"]}

// Check for mobile-specific issues (touch events, viewport, etc.)
proxylog {proxy_id: "mobile", types: ["http"], url_pattern: "/api"}

// View page sessions from mobile
currentpage {proxy_id: "mobile"}

BrowserStack Integration

For automated testing across many devices, combine agnt with BrowserStack's MCP server.

Setup Both MCP Servers

claude_desktop_config.json
{
"mcpServers": {
"agnt": {
"command": "agnt",
"args": ["mcp"]
},
"browserstack": {
"command": "npx",
"args": ["@anthropic-ai/browserstack-mcp"],
"env": {
"BROWSERSTACK_USERNAME": "your_username",
"BROWSERSTACK_ACCESS_KEY": "your_key",
"BROWSERSTACK_LOCAL": "true"
}
}
}
}

Workflow: Automated Device Testing

// 1. Start instrumented proxy with tunnel
proxy {action: "start", id: "test", target_url: "http://localhost:3000", bind_address: "0.0.0.0"}
tunnel {action: "start", id: "test", provider: "cloudflare", local_port: 45849, proxy_id: "test"}
// Response: {public_url: "https://abc.trycloudflare.com"}

// 2. Use BrowserStack MCP to run tests on the tunnel URL
// (BrowserStack MCP commands would go here)

// 3. After tests, check captured data from all devices:
proxylog {proxy_id: "test", types: ["error"]} // All JS errors
proxylog {proxy_id: "test", types: ["performance"]} // Load times per device
currentpage {proxy_id: "test"} // Page sessions

What You Get

From agntFrom BrowserStack
JS error stack tracesScreenshots
HTTP request/response logsVideo recordings
Performance metricsDevice-specific logs
User interactionsAutomated test results
DOM state captureCross-browser coverage

Debugging Tips for Remote Devices

1. Check Connection Status

The floating indicator shows connection status with a green/red dot. If red:

  • Verify the tunnel is running: tunnel {action: "status", id: "app"}
  • Check WebSocket connection in browser console

2. Mobile-Specific Diagnostics

// Check viewport and responsive issues
proxy {action: "exec", id: "app", code: "window.__devtool.auditResponsive()"}

// Check touch targets (buttons too small?)
proxy {action: "exec", id: "app", code: "window.__devtool.findSmallTouchTargets()"}

// Check text readability
proxy {action: "exec", id: "app", code: "window.__devtool.checkTextFragility()"}

3. Capture Device State

// When a user reports an issue, capture everything:
proxy {action: "exec", id: "app", code: "window.__devtool.captureState()"}
// Returns: localStorage, sessionStorage, cookies, viewport, URL, etc.

4. Clean Up After Testing

tunnel {action: "stop", id: "mobile"}
proxy {action: "stop", id: "mobile"}
proc {action: "stop", process_id: "dev"}

Security Notes

  • Development Only - No authentication, allows all origins
  • Body Truncation - Request/response bodies limited to 10KB in logs
  • Local Traffic - Only proxy trusted local development servers
  • Tunnel Security - Tunnels expose your dev server publicly; stop them when not needed

Next Steps