js-reverse-mcp-debugging
Skill by
ara.so — MCP Skills collection.
Overview
js-reverse-mcp is an MCP server that gives AI agents full JavaScript debugging capabilities: breakpoints, call stacks, scope inspection, network analysis, and WebSocket message capture. Built on Patchright (CDP protocol anti-detection) with optional CloakBrowser (49 C++ fingerprint patches) for strong anti-bot sites.
Key features:
- Headful debugging — visible browser, breakpoints, step-through, call stacks
- Persistent sessions — cookies/localStorage survive restarts
- Dual anti-detection — Patchright (protocol layer) + optional CloakBrowser (binary patches)
- 21 MCP tools — script analysis, breakpoint control, network inspection, WebSocket analysis
- Zero JS injection — no hacks that leak automation signals
Installation
NPX (Recommended)
Add to your MCP client configuration:
json
{
"mcpServers": {
"js-reverse": {
"command": "npx",
"args": ["js-reverse-mcp"]
}
}
}
Claude Code:
bash
claude mcp add js-reverse npx js-reverse-mcp
Codex:
bash
codex mcp add js-reverse -- npx js-reverse-mcp
Local Install
bash
git clone https://github.com/zhizhuodemao/js-reverse-mcp.git
cd js-reverse-mcp
npm install
npm run build
Then configure with local path:
json
{
"mcpServers": {
"js-reverse": {
"command": "node",
"args": ["/path/to/js-reverse-mcp/build/src/index.js"]
}
}
}
Configuration Options
CLI flags (all optional):
- — Use CloakBrowser binary with 49 C++ fingerprint patches (auto-downloads ~200MB on first run)
- — Use temporary profile (no persistent cookies/localStorage)
- — Connect to existing Chrome instance (CDP endpoint, e.g. )
- — Write debug logs to file (use with env var)
Common Configurations
Default (System Chrome + Persistent Login):
json
{
"mcpServers": {
"js-reverse": {
"command": "npx",
"args": ["js-reverse-mcp"]
}
}
}
Anti-Bot Sites (Cloudflare, DataDome, FingerprintJS):
Pre-download CloakBrowser binary first (one-time, ~30-60s):
Then configure:
json
{
"mcpServers": {
"js-reverse-cloak": {
"command": "npx",
"args": ["js-reverse-mcp", "--cloak"]
}
}
}
Dual Setup (Switch Based on Target):
json
{
"mcpServers": {
"js-reverse": {
"command": "npx",
"args": ["js-reverse-mcp"]
},
"js-reverse-cloak": {
"command": "npx",
"args": ["js-reverse-mcp", "--cloak"]
}
}
}
Connect to Running Chrome:
-
Launch Chrome with debugging:
bash
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="%TEMP%\chrome-debug"
-
Configure MCP:
json
{
"mcpServers": {
"js-reverse": {
"command": "npx",
"args": ["js-reverse-mcp", "--browserUrl", "http://127.0.0.1:9222"]
}
}
}
MCP Tools (21)
Page & Navigation
| Tool | Purpose |
|---|
| List open pages or switch debugging context by index |
| Create new page and navigate to URL |
| Navigate, back, forward, or refresh |
| List/select iframe execution context |
| Capture page screenshot |
Script Analysis
| Tool | Purpose |
|---|
| List all loaded JavaScript files |
| Fetch script source (supports line ranges, character offsets) |
| Save full script to local file (large/minified/WASM) |
| Search all scripts for string/regex |
Breakpoints & Execution
| Tool | Purpose |
|---|
| Set breakpoint by searching code text (works in minified code) |
| Set XHR/Fetch breakpoint by URL pattern |
| Remove by ID, URL, or all; auto-resumes execution |
| List all active breakpoints |
| Get pause state, call stack, scope variables |
| Toggle pause/resume |
| Step over/into/out, returns location + source context |
Network & WebSocket
| Tool | Purpose |
|---|
| List requests or get single request details by reqid |
| Get JavaScript call stack for network request |
| List connections, analyze message patterns, get message details |
Inspection
| Tool | Purpose |
|---|
| Execute JavaScript (supports breakpoint context, main world, save results/binary to file) |
| List console messages or get single message by msgid |
Common Workflows
1. Basic Reverse Engineering
typescript
// User: "Debug the encryption on example.com"
// Step 1: Open target page
await use_mcp_tool("js-reverse", "new_page", {
url: "https://example.com"
});
// Step 2: Find encryption functions
const searchResults = await use_mcp_tool("js-reverse", "search_in_sources", {
query: "encrypt|crypto|AES|cipher",
isRegex: true
});
// Step 3: Set breakpoint on encryption function
await use_mcp_tool("js-reverse", "set_breakpoint_on_text", {
searchText: "function encrypt(data)",
scriptUrl: searchResults[0].url
});
// Step 4: Trigger action (user does this in browser or via evaluate_script)
// ... breakpoint hits ...
// Step 5: Inspect paused state
const pausedInfo = await use_mcp_tool("js-reverse", "get_paused_info", {});
// Returns: call stack, scope variables, current location
// Step 6: Evaluate in breakpoint context
const params = await use_mcp_tool("js-reverse", "evaluate_script", {
expression: "data",
returnByValue: true
});
// Step 7: Step through execution
await use_mcp_tool("js-reverse", "step", {
action: "into" // or "over", "out"
});
2. Network Request Analysis
typescript
// User: "Find where this API request is initiated"
// Step 1: List network requests
const requests = await use_mcp_tool("js-reverse", "list_network_requests", {});
// Step 2: Find target request
const apiRequest = requests.find(r => r.url.includes("/api/user"));
// Step 3: Get JavaScript call stack
const initiator = await use_mcp_tool("js-reverse", "get_request_initiator", {
reqid: apiRequest.reqid
});
// Returns: full JS stack trace from initiation point
// Step 4: Set breakpoint at initiation
await use_mcp_tool("js-reverse", "set_breakpoint_on_text", {
searchText: initiator.callFrames[0].functionName,
scriptUrl: initiator.callFrames[0].url
});
3. XHR/Fetch Interception
typescript
// User: "Break on all requests to /api/encrypt"
// Set XHR breakpoint with URL pattern
await use_mcp_tool("js-reverse", "break_on_xhr", {
urlPattern: "*/api/encrypt*"
});
// Execution will pause before matching XHR/fetch
// Then inspect request body, headers, call stack
const pausedInfo = await use_mcp_tool("js-reverse", "get_paused_info", {});
// Evaluate request payload
const payload = await use_mcp_tool("js-reverse", "evaluate_script", {
expression: "arguments[0]", // xhr.send() argument
returnByValue: true
});
4. WebSocket Protocol Analysis
typescript
// User: "Analyze the WebSocket messages for this trading site"
// Step 1: List WebSocket connections
const wsData = await use_mcp_tool("js-reverse", "get_websocket_messages", {
action: "list"
});
// Step 2: Analyze message patterns
const analysis = await use_mcp_tool("js-reverse", "get_websocket_messages", {
action: "analyze",
wsid: wsData.connections[0].wsid
});
// Returns: message type distribution, size stats, timing patterns
// Step 3: Inspect specific message
const message = await use_mcp_tool("js-reverse", "get_websocket_messages", {
action: "get",
wsid: wsData.connections[0].wsid,
msgid: "msg_123"
});
// Returns: full payload, timestamp, direction (sent/received)
5. Minified Code Debugging
typescript
// User: "Set breakpoint on the obfuscated validation function"
// Step 1: Search for function signature in minified code
const results = await use_mcp_tool("js-reverse", "search_in_sources", {
query: "validate.*password",
isRegex: true
});
// Step 2: Get context around match
const source = await use_mcp_tool("js-reverse", "get_script_source", {
scriptId: results[0].scriptId,
startOffset: results[0].match.offset - 200,
endOffset: results[0].match.offset + 200
});
// Step 3: Set breakpoint by text match (works in minified code)
await use_mcp_tool("js-reverse", "set_breakpoint_on_text", {
searchText: results[0].match.line.trim(),
scriptUrl: results[0].url,
condition: "password.length > 0" // optional conditional breakpoint
});
6. Scope Variable Inspection
typescript
// After hitting breakpoint, inspect all accessible variables
const pausedInfo = await use_mcp_tool("js-reverse", "get_paused_info", {});
// pausedInfo.scopeChain contains:
// - local variables
// - closure variables
// - global scope
// Evaluate complex expressions in current scope
const result = await use_mcp_tool("js-reverse", "evaluate_script", {
expression: "Object.keys(this).filter(k => k.startsWith('_'))",
callFrameId: pausedInfo.callFrames[0].callFrameId,
returnByValue: true
});
7. Save Large Script Sources
typescript
// User: "Save this 5MB minified bundle for analysis"
const scripts = await use_mcp_tool("js-reverse", "list_scripts", {});
const targetScript = scripts.find(s => s.url.includes("bundle.min.js"));
// Save to local file (more reliable than get_script_source for large files)
await use_mcp_tool("js-reverse", "save_script_source", {
scriptId: targetScript.scriptId,
outputPath: "/tmp/bundle.min.js"
});
// Now analyze with external tools or search within it
const matches = await use_mcp_tool("js-reverse", "search_in_sources", {
query: "apiKey.*=.*['\"]([^'\"]+)['\"]",
isRegex: true,
scriptId: targetScript.scriptId
});
8. Multi-Page Debugging
typescript
// User: "Debug login flow across multiple redirects"
// Step 1: List all open pages
const pages = await use_mcp_tool("js-reverse", "select_page", {});
// Step 2: Switch to login page
await use_mcp_tool("js-reverse", "select_page", {
index: 0
});
// Step 3: Set breakpoint on form submit
await use_mcp_tool("js-reverse", "set_breakpoint_on_text", {
searchText: "submitLogin"
});
// Step 4: After redirect, switch to new page
const updatedPages = await use_mcp_tool("js-reverse", "select_page", {});
await use_mcp_tool("js-reverse", "select_page", {
index: updatedPages.length - 1
});
// Step 5: Continue debugging in new context
const scripts = await use_mcp_tool("js-reverse", "list_scripts", {});
Troubleshooting
Bot Detection / Access Denied
Symptoms: Site returns 403, Cloudflare challenge loops, Zhihu 40362 error
Solution 1: Try isolated profile first (rules out state pollution)
json
"args": ["js-reverse-mcp", "--isolated"]
Solution 2: Enable CloakBrowser (49 fingerprint patches)
Pre-download binary:
Configure:
json
"args": ["js-reverse-mcp", "--cloak"]
Solution 3: Clear persistent profile (loses login state)
bash
rm -rf ~/.cache/chrome-devtools-mcp/chrome-profile
# or for cloak mode:
rm -rf ~/.cache/chrome-devtools-mcp/cloak-profile
Breakpoint Not Hitting
-
Check if script is loaded:
typescript
const scripts = await use_mcp_tool("js-reverse", "list_scripts", {});
// Verify target script is in list
-
Use text-based breakpoint (works in minified code):
typescript
await use_mcp_tool("js-reverse", "set_breakpoint_on_text", {
searchText: "unique_code_snippet",
scriptUrl: "target.js"
});
-
List active breakpoints:
typescript
const breakpoints = await use_mcp_tool("js-reverse", "list_breakpoints", {});
-
Check if execution is paused elsewhere:
typescript
const pausedInfo = await use_mcp_tool("js-reverse", "get_paused_info", {});
Cannot Find Script
If dynamic/lazy-loaded scripts don't appear:
-
Navigate to trigger script load:
typescript
await use_mcp_tool("js-reverse", "navigate_page", {
action: "goto",
url: "https://example.com/trigger-page"
});
-
Wait for script load, then list:
typescript
// Give page time to load
await new Promise(resolve => setTimeout(resolve, 2000));
const scripts = await use_mcp_tool("js-reverse", "list_scripts", {});
-
Search across all sources:
typescript
const results = await use_mcp_tool("js-reverse", "search_in_sources", {
query: "function_name"
});
WebSocket Messages Not Captured
-
Ensure page is loaded and WebSocket is connected:
typescript
const wsData = await use_mcp_tool("js-reverse", "get_websocket_messages", {
action: "list"
});
// Check if connections array is populated
-
Trigger WebSocket traffic in browser (message capture is passive)
-
List connections includes lifecycle events; messages are captured automatically once connection is established
Evaluate Script Fails
Common issues:
-
Execution context invalid — Make sure you're evaluating in the right frame:
typescript
// List frames first
const frames = await use_mcp_tool("js-reverse", "select_frame", {});
// Select target frame
await use_mcp_tool("js-reverse", "select_frame", {
index: 1 // switch to iframe
});
// Now evaluate
await use_mcp_tool("js-reverse", "evaluate_script", {
expression: "window.secretVar"
});
-
Paused in wrong call frame — Specify
:
typescript
const pausedInfo = await use_mcp_tool("js-reverse", "get_paused_info", {});
await use_mcp_tool("js-reverse", "evaluate_script", {
expression: "localVar",
callFrameId: pausedInfo.callFrames[0].callFrameId
});
-
Reference error — Variable not in scope; check
:
typescript
const pausedInfo = await use_mcp_tool("js-reverse", "get_paused_info", {});
// Inspect pausedInfo.scopeChain to see available variables
Anti-Detection Layer Details
Protocol Layer (Always Active):
- Patchright removes , CDP calls
- Script evaluation in isolated world
- Automation launch flags stripped
- 49 C++ patches: , canvas, WebGL, audio, GPU, fonts, screen, WebRTC, TLS
- Custom Chromium build (no Google services, no Web Store)
- Persistent fingerprint identity per profile
Navigation Layer (Always Active):
- Silent CDP navigation (no early /)
- Google referer on
- Real viewport (no fake 1280×720)
When to use : Only when protocol-layer evasion fails. See project docs for details.
Best Practices
- Start simple: Use default mode; only add if blocked
- Persistent sessions: Default profile saves login state (use for clean state)
- Text-based breakpoints: More reliable than line numbers in minified/dynamic code
- Save large sources: Use instead of for big files
- Scope inspection: Always check
get_paused_info().scopeChain
before evaluating variables
- Network initiators: Use to find where requests originate
- WebSocket analysis: Use action first to understand message patterns before diving into individual messages
Security Warning
This tool exposes browser content to MCP clients with full inspection/modification capabilities. Do not use on pages with sensitive information (passwords, payment details, private data).