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:
- HTTP Proxy - All traffic is forwarded to your dev server
- Traffic Logging - Requests and responses are captured
- JS Injection - Diagnostic JavaScript is added to HTML pages
- WebSocket Server - Receives metrics from instrumented frontend
- 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
| Action | Description |
|---|---|
| Full Page Audit | Comprehensive audit with A-F grade and recommendations |
| Accessibility | WCAG issues (missing alt, labels, contrast) |
| Security | Mixed content, XSS risks, missing noopener |
| SEO / Meta | Meta tags, headings, document structure |
Layout & Visual
| Action | Description |
|---|---|
| Layout Issues | Overflows, z-index contexts, offscreen elements |
| Text Fragility | Truncation, overflow, font issues (WCAG 1.4.10) |
| Responsive Risk | Elements that may break at different viewport sizes |
Debug Context
| Action | Description |
|---|---|
| Last Click Context | What the user just clicked + mouse trail |
| Recent DOM Changes | Elements added/removed/modified in last 30 seconds |
State & Network
| Action | Description |
|---|---|
| Browser State | localStorage, sessionStorage, cookies |
| Network/Resources | Resource timing and loading data |
Technical
| Action | Description |
|---|---|
| DOM Complexity | Node count, depth, performance impact |
| CSS Quality | Inline styles, !important usage |
When you run an audit, the results are:
- Executed immediately in the browser
- Summarized as a chip in the message area
- 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
- Use Meaningful IDs -
frontend,api,stagingnotproxy1 - Check listen_addr - Port may be auto-assigned if busy
- Clear Old Sessions -
currentpage {action: "clear"}periodically - Monitor Dropped Logs - Check stats for
droppedcount - 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.
Cloudflare Quick Tunnels (Recommended)
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
{
"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 agnt | From BrowserStack |
|---|---|
| JS error stack traces | Screenshots |
| HTTP request/response logs | Video recordings |
| Performance metrics | Device-specific logs |
| User interactions | Automated test results |
| DOM state capture | Cross-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
- Explore the Frontend Diagnostics API
- See Mobile Testing Guide for complete workflow
- Read the tunnel API Reference for all options
- See Debugging Web Apps use case
- Learn about Performance Monitoring