AI Assistants That Can Actually See Your Browser Console
How I Gave My AI Coding Agents Eyes Into Chrome DevTools
If you’re developing web applications with AI coding assistants like Claude, Cline, or Continue.dev, you’ve probably found yourself doing this dance dozens of times a day:
AI suggests a fix
You implement it
Browser console shows errors
You copy/paste the errors back to the AI
Repeat
What if your AI could just... see the console errors directly? And not just see them, but interact with the browser to test your app?
That’s what I built, and I’m sharing the complete implementation so you can set it up too.
The Problem: AI Assistants Are Blind to Browser State
Modern AI coding assistants are incredibly helpful, but they have a critical blindness: they can’t see what’s happening in your browser.
Here’s what this looks like in practice:
Me: “The login page isn’t working” AI: “Let me check the code... looks fine. Any errors in the console?” Me: Opens DevTools, copies 6 different errors AI: “Ah, I see the issue now...”
This copy-paste workflow breaks flow state and wastes time. Even worse, it’s incomplete—I might miss important context like:
Network request failures
Timing of errors
Stack traces
WebSocket connection issues
And there’s a bigger problem: AI can’t test its own fixes. After implementing a solution, I’m back to manual testing and more copy-pasting.
The Solution: Model Context Protocol + Chrome DevTools Protocol
The fix combines two powerful protocols:
Model Context Protocol (MCP) - Anthropic’s open standard for connecting AI assistants to external tools
Chrome DevTools Protocol (CDP) - Chrome’s built-in remote debugging interface
What This Enables
Monitoring Capabilities:
✅ Real-time console error monitoring
✅ Network request tracking
✅ JavaScript exceptions with stack traces
✅ WebSocket connection status
Browser Automation:
✅ Navigate to pages
✅ Click buttons and links
✅ Select from dropdowns
✅ Execute JavaScript
✅ Take screenshots
⚠️ Text input (limited - more on this later)
Architecture Overview
Chrome (--remote-debugging-port=9222)
↓
Chrome DevTools Protocol (WebSocket)
↓
Python MCP Server (chrome-devtools-mcp/)
↓
Model Context Protocol
↓
AI Assistant (Claude Code, Cline, etc.)
Step-by-Step Implementation
Prerequisites
Python 3.7+ installed
Chrome browser
AI assistant that supports MCP (Claude Code, Claude Desktop, Cline, Continue.dev, etc.)
Node.js (if you’re developing web apps)
Step 1: Set Up Chrome Debug Profile
First, create an isolated Chrome profile for debugging. This keeps your development work separate from your regular browsing.
Create this directory structure:
your-project/
├── chrome-debug-tools/
│ ├── chrome-debug-profile/ # Chrome will create this
│ └── START-DEBUG-SESSION.bat # We’ll create this
START-DEBUG-SESSION.bat (Windows):
@echo off
echo ========================================
echo Chrome DevTools MCP - Debug Session
echo ========================================
echo.
echo Options:
echo 1. Open Inti app in debug Chrome
echo 2. Check if debugging is ready
echo 3. Monitor console errors (live)
echo 4. Exit
echo.
set /p choice=”Enter choice (1-4): “
if “%choice%”==”1” (
echo.
echo Starting Chrome with debugging enabled...
echo Port: 9222
echo Profile: chrome-debug-profile/
echo.
start chrome --remote-debugging-port=9222 --user-data-dir=”%~dp0chrome-debug-profile” “http://localhost:5173”
echo.
echo Chrome started! You can now ask AI to monitor console.
pause
) else if “%choice%”==”2” (
echo.
echo Checking debug status...
curl -s “http://127.0.0.1:9222/json/version” > nul 2>&1
if %errorlevel% equ 0 (
echo [OK] Chrome debugging is running on port 9222
curl -s “http://127.0.0.1:9222/json/list” | find “http” > nul
if %errorlevel% equ 0 (
echo [OK] Pages detected
)
) else (
echo [ERROR] Chrome debugging is not running
echo Run option 1 first
)
pause
) else if “%choice%”==”3” (
cd /d “%~dp0..\workspace”
python live-error-monitor.py
) else (
exit
)
For Mac/Linux, create START-DEBUG-SESSION.sh
:
#!/bin/bash
echo “========================================”
echo “Chrome DevTools MCP - Debug Session”
echo “========================================”
echo “”
echo “Options:”
echo “1. Open app in debug Chrome”
echo “2. Check if debugging is ready”
echo “3. Monitor console errors (live)”
echo “4. Exit”
echo “”
read -p “Enter choice (1-4): “ choice
if [ “$choice” = “1” ]; then
echo “”
echo “Starting Chrome with debugging enabled...”
echo “Port: 9222”
echo “”
# Mac
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=”$(dirname “$0”)/chrome-debug-profile” \
“http://localhost:5173” &
echo “Chrome started!”
elif [ “$choice” = “2” ]; then
echo “”
echo “Checking debug status...”
curl -s “http://127.0.0.1:9222/json/version” > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo “[OK] Chrome debugging is running on port 9222”
else
echo “[ERROR] Chrome debugging is not running”
fi
fi
Step 2: Install Chrome DevTools MCP Server
Clone or download the MCP server:
# Option 1: From the official MCP servers repository
git clone https://github.com/modelcontextprotocol/servers.git
cd servers/src/chrome-devtools
# Option 2: Create from scratch (I’ll provide the code)
Key files needed:
server.py - The MCP server that bridges Chrome CDP to MCP:
#!/usr/bin/env python3
import asyncio
import json
import aiohttp
from typing import Any
import sys
# MCP server implementation
# (Full code available in the GitHub repository)
Install dependencies:
pip install mcp aiohttp websockets
Step 3: Configure Your AI Assistant
For Claude Code (CLI):
claude mcp add chrome-devtools python “C:\path\to\chrome-devtools-mcp\server.py” --env CHROME_DEBUG_PORT=9222
Verify it’s connected:
claude mcp list
# Should show: chrome-devtools: ✓ Connected
For Claude Desktop:
Edit %APPDATA%\Claude\claude_desktop_config.json
(Windows) or ~/Library/Application Support/Claude/claude_desktop_config.json
(Mac):
{
“mcpServers”: {
“chrome-devtools”: {
“command”: “python”,
“args”: [”C:\\path\\to\\chrome-devtools-mcp\\server.py”],
“env”: {
“CHROME_DEBUG_PORT”: “9222”
}
}
}
}
For Cline/Continue.dev:
Check their MCP configuration documentation - the setup is similar but uses their specific config files.
Step 4: Create Browser Automation Tools
Create workspace/browser-control.py:
“”“
Browser automation via Chrome DevTools Protocol
Usage: python browser-control.py <command> [args]
“”“
import asyncio
import aiohttp
import websockets
import json
import sys
class BrowserController:
def __init__(self, target_id=None):
self.target_id = target_id
self.ws = None
self.message_id = 0
async def connect(self):
“”“Connect to Chrome DevTools”“”
if not self.target_id:
self.target_id = await self.find_target()
async with aiohttp.ClientSession() as session:
async with session.get(f’http://127.0.0.1:9222/json’) as resp:
targets = await resp.json()
target = next((t for t in targets if t[’id’] == self.target_id), None)
if not target:
raise Exception(”Target not found”)
ws_url = target[’webSocketDebuggerUrl’]
self.ws = await websockets.connect(ws_url)
async def find_target(self):
“”“Find the app page target”“”
async with aiohttp.ClientSession() as session:
async with session.get(’http://127.0.0.1:9222/json/list’) as resp:
targets = await resp.json()
for target in targets:
if target.get(’type’) == ‘page’:
return target[’id’]
return None
async def send_command(self, method, params=None):
“”“Send CDP command”“”
self.message_id += 1
message = {
‘id’: self.message_id,
‘method’: method,
‘params’: params or {}
}
await self.ws.send(json.dumps(message))
while True:
response = json.loads(await self.ws.recv())
if response.get(’id’) == self.message_id:
return response.get(’result’, {})
async def navigate(self, url):
“”“Navigate to URL”“”
print(f”🌐 Navigating to: {url}”)
result = await self.send_command(’Page.navigate’, {’url’: url})
await asyncio.sleep(2)
print(”✓ Page loaded”)
return result
async def execute_js(self, expression, return_value=True):
“”“Execute JavaScript”“”
print(f”💻 Executing: {expression[:60]}...”)
result = await self.send_command(’Runtime.evaluate’, {
‘expression’: expression,
‘returnByValue’: return_value,
‘awaitPromise’: True
})
value = result.get(’result’, {}).get(’value’)
print(f”✓ Result: {value}”)
return value
async def take_screenshot(self, filename):
“”“Take screenshot”“”
print(”📸 Taking screenshot...”)
result = await self.send_command(’Page.captureScreenshot’, {’format’: ‘png’})
import base64
image_data = base64.b64decode(result[’data’])
with open(filename, ‘wb’) as f:
f.write(image_data)
print(f”✓ Screenshot saved: {filename}”)
async def get_page_info(self):
“”“Get current page info”“”
return await self.execute_js(”“”
({
url: window.location.href,
title: document.title,
readyState: document.readyState,
bodyText: document.body.innerText.substring(0, 200)
})
“”“)
async def close(self):
“”“Close connection”“”
if self.ws:
await self.ws.close()
async def main():
if len(sys.argv) < 2:
print(__doc__)
print(”\nCommands:”)
print(” navigate <url>”)
print(” execute <js>”)
print(” screenshot <file>”)
print(” info”)
return
command = sys.argv[1]
browser = BrowserController()
await browser.connect()
try:
if command == ‘navigate’:
await browser.navigate(sys.argv[2])
elif command == ‘execute’:
await browser.execute_js(sys.argv[2])
elif command == ‘screenshot’:
await browser.take_screenshot(sys.argv[2])
elif command == ‘info’:
info = await browser.get_page_info()
print(json.dumps(info, indent=2))
finally:
await browser.close()
if __name__ == ‘__main__’:
asyncio.run(main())
Create workspace/live-error-monitor.py:
“”“
Live console error monitor
Connects to Chrome DevTools and streams console errors
“”“
import asyncio
import aiohttp
import websockets
import json
import sys
async def monitor_console():
print(”🔍 Starting console monitor...”)
print(”Connecting to Chrome DevTools on port 9222...”)
# Get the first page target
async with aiohttp.ClientSession() as session:
async with session.get(’http://127.0.0.1:9222/json/list’) as resp:
targets = await resp.json()
target = next((t for t in targets if t.get(’type’) == ‘page’), None)
if not target:
print(”❌ No page found”)
return
ws_url = target[’webSocketDebuggerUrl’]
print(f”✓ Connected to: {target[’title’]}”)
print(”\n” + “=”*70)
print(”📡 MONITORING CONSOLE (Press Ctrl+C to stop)”)
print(”=”*70 + “\n”)
async with websockets.connect(ws_url) as ws:
# Enable console and network domains
await ws.send(json.dumps({’id’: 1, ‘method’: ‘Console.enable’}))
await ws.send(json.dumps({’id’: 2, ‘method’: ‘Network.enable’}))
await ws.send(json.dumps({’id’: 3, ‘method’: ‘Runtime.enable’}))
# Consume initial responses
for _ in range(3):
await ws.recv()
# Listen for events
while True:
message = json.loads(await ws.recv())
method = message.get(’method’, ‘’)
params = message.get(’params’, {})
# Console errors
if method == ‘Console.messageAdded’:
msg = params.get(’message’, {})
if msg.get(’level’) in [’error’, ‘warning’]:
level = msg.get(’level’, ‘log’).upper()
text = msg.get(’text’, ‘’)
url = msg.get(’url’, ‘unknown’)
line = msg.get(’line’, 0)
icon = ‘❌’ if level == ‘ERROR’ else ‘⚠️’
print(f”{icon} {level} @ {url}:{line}”)
print(f” {text}”)
print()
# Runtime exceptions
elif method == ‘Runtime.exceptionThrown’:
exception = params.get(’exceptionDetails’, {})
text = exception.get(’text’, ‘Unknown error’)
line = exception.get(’lineNumber’, 0)
column = exception.get(’columnNumber’, 0)
print(f”💥 UNCAUGHT EXCEPTION @ line {line}:{column}”)
print(f” {text}”)
print()
# Network failures
elif method == ‘Network.loadingFailed’:
request_id = params.get(’requestId’)
error_text = params.get(’errorText’, ‘Unknown’)
print(f”🌐 NETWORK FAILED”)
print(f” {error_text}”)
print()
if __name__ == ‘__main__’:
try:
asyncio.run(monitor_console())
except KeyboardInterrupt:
print(”\n\n✓ Monitoring stopped”)
Step 5: Update .gitignore
Add these lines to your .gitignore
:
# Chrome debug profile (browser cache and user data)
chrome-debug-tools/chrome-debug-profile/
# Workspace temporary files
workspace/__pycache__/
workspace/*.png
Agent Guidance: Teaching Your AI to Use These Tools
Create chrome-debug-tools/AGENT-INSTRUCTIONS.md:
# Chrome DevTools MCP - Agent Instructions
## Quick Reference
You have access to Chrome DevTools. You can:
- Monitor browser console in real-time
- Navigate pages and click elements
- Execute JavaScript
- Take screenshots
- Test user flows
## Common Workflows
### Workflow 1: “Check console for errors”
```bash
# 1. Start monitor
cd workspace && python live-error-monitor.py
# 2. Ask user to refresh page
# 3. Parse output and report errors with:
# - Error type (WebSocket, HTTP, Exception)
# - File and line number
# - Root cause analysis
# - Suggested fix
```
### Workflow 2: “Test the login page”
```bash
# 1. Navigate
python browser-control.py navigate “https://app-url.com/login”
# 2. Take screenshot
python browser-control.py screenshot login-page.png
# 3. Execute JavaScript to check page state
python browser-control.py execute “({hasForm: !!document.querySelector(’form’), hasErrors: !!document.querySelector(’.error’)})”
# 4. Report findings
```
### Workflow 3: “Test dropdown selection”
```python
# Custom Python script for complex interactions
import asyncio
from browser_control import BrowserController
async def test_dropdown():
browser = BrowserController()
await browser.connect()
# Click dropdown
await browser.execute_js(”“”
document.querySelector(’button.dropdown’).click()
“”“)
await asyncio.sleep(1)
# Select option
await browser.execute_js(”“”
document.querySelector(’[role=”option”]’).click()
“”“)
# Take screenshot
await browser.take_screenshot(’dropdown-selected.png’)
await browser.close()
asyncio.run(test_dropdown())
```
## Known Limitations
### Text Input with React Forms
**Issue**: Text input automation has limitations with React controlled components. While CDP can send keystrokes and JavaScript can set `input.value`, React doesn’t recognize these changes because they bypass React’s synthetic event system.
**What works:**
- ✅ Clicking buttons and links
- ✅ Selecting from dropdowns (via click)
- ✅ Reading input values
**What doesn’t work:**
- ❌ Typing text into React controlled inputs
- ❌ Filling forms that use React state validation
**Workarounds:**
- Ask user to manually fill text fields
- Use JavaScript to bypass validation (testing only)
- For production automation, recommend Playwright
## Test Results
Successfully automated dropdown selection on /create page:
- ✅ Selected “Collaboration” from Type dropdown
- ✅ Selected “Arts and Humanities” from Category
- ✅ Selected “Academic” from Audience
- ❌ Could not fill text inputs (React limitation)
Real-World Testing: What Works (and What Doesn’t)
I tested this setup on a real Next.js/React application with a complex form. Here’s what happened:
✅ What Worked Perfectly
Console Monitoring:
❌ ERROR @ useUCO.tsx:212
WebSocket connection failed: Error connecting to wss://...
🌐 NETWORK FAILED
401 Unauthorized: /api/session/whoami
The AI could see these errors in real-time and immediately suggest fixes. No copy-pasting needed.
Dropdown Automation: I asked the AI to “navigate to /create and select from the dropdowns.” It successfully:
Navigated to the page
Found 3 custom dropdown components
Clicked each one
Selected options from all three
Took screenshots as evidence
JavaScript Execution:
// AI ran this to verify page state
({
type: document.querySelector(’[role=”combobox”]’).textContent,
hasErrors: !!document.querySelector(’.error’),
formValid: document.querySelector(’button[type=”submit”]’).disabled === false
})
⚠️ What Didn’t Work: Text Input
The one limitation I hit: text input in React controlled components.
The AI tried to fill form fields:
const input = document.querySelector(’input[name=”title”]’);
input.value = ‘Test Title’;
input.dispatchEvent(new Event(’input’, { bubbles: true }));
The value appeared briefly but didn’t persist. React’s state management doesn’t recognize programmatic value changes that don’t go through its synthetic event system.
Current workaround: Manual text input, then AI automates the rest.
Looking for solutions: If you’ve solved this problem (maybe with a different CDP approach or React-specific hack), please share in the comments!
Remaining Issues & Call for Feedback
Known Limitations
React Controlled Input - As mentioned, can’t reliably fill text inputs
File Uploads - Haven’t tested yet, likely similar issues
Drag and Drop - Probably won’t work via CDP
Cross-Origin Content - Can’t interact with iframes from different origins
Questions for the Community
Has anyone solved the React input problem? Maybe there’s a CDP command I’m missing, or a way to properly trigger React’s onChange?
Mobile debugging? This setup uses desktop Chrome. Has anyone connected this to mobile Chrome debugging?
Other AI assistants? I’ve tested with Claude Code. If you try this with Cline, Continue.dev, or other MCP-compatible tools, please share your experience!
Better error parsing? My console monitor is pretty basic. Any suggestions for better error categorization or filtering?
CI/CD integration? Could this pattern work in automated testing pipelines? Headless Chrome + MCP server + AI test generation?
Why This Matters
This isn’t just about convenience (though never copy-pasting console errors again is pretty sweet). It fundamentally changes how AI assistants can help with debugging:
Before: AI suggests fixes blindly After: AI sees the error, tests the fix, verifies it worked
Before: Manual testing after every change After: AI can automate test flows and report results
Before: Context switching between code and browser After: AI handles the context switching for you
It’s like pair programming, but your pair can see your screen.
Get Started
All the code from this post is available in my project repository. The setup takes about 30 minutes, and once it’s running, you’ll wonder how you ever debugged without it.
MCP Servers: https://github.com/modelcontextprotocol/servers
Chrome DevTools Protocol Docs: https://chromedevtools.github.io/devtools-protocol/
Your Turn
Have you set this up? Run into issues? Found improvements? Drop a comment below. I’m especially interested in:
Solutions to the React input limitation
Your experience with other AI assistants
Use cases I haven’t thought of
Horror stories (we all have them)
Let’s figure this out together. The future of AI-assisted development is collaborative, and I’d love to hear what you build with this.
Steven Vincent is building Inti, a collaborative knowledge platform with AI-powered voice interface. When not wrangling WebSockets and React state, he’s exploring how AI can augment human creativity without replacing it. Follow along at www.TheSingularityProject.AI
Changelog
2025-10-06: Initial implementation and testing
✅ Console monitoring working
✅ Browser automation (navigation, clicks, screenshots)
✅ Dropdown selection tested successfully
⚠️ Text input limitation documented
Got updates or fixes? I’ll keep this post current with reader contributions.