┌──────────────┐ CDP (WebSocket) ┌──────────────────┐
│ Your CLI │ ◄──────────────────────► │ Chrome Browser │
│ (Node.js) │ │ (--remote-debug) │
└──────┬───────┘ └──────────────────┘ │ │ Optional: MCP Bridge │
┌──────▼───────┐
│ AI Agent │
│ (LLM/Tool) │
└──────────────┘
┌──────────────┐ CDP (WebSocket) ┌──────────────────┐
│ Your CLI │ ◄──────────────────────► │ Chrome Browser │
│ (Node.js) │ │ (--remote-debug) │
└──────┬───────┘ └──────────────────┘ │ │ Optional: MCP Bridge │
┌──────▼───────┐
│ AI Agent │
│ (LLM/Tool) │
└──────────────┘
┌──────────────┐ CDP (WebSocket) ┌──────────────────┐
│ Your CLI │ ◄──────────────────────► │ Chrome Browser │
│ (Node.js) │ │ (--remote-debug) │
└──────┬───────┘ └──────────────────┘ │ │ Optional: MCP Bridge │
┌──────▼───────┐
│ AI Agent │
│ (LLM/Tool) │
└──────────────┘
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --remote-debugging-port=9222
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --remote-debugging-port=9222
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \ --remote-debugging-port=9222
google-chrome --remote-debugging-port=9222
google-chrome --remote-debugging-port=9222
google-chrome --remote-debugging-port=9222
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
curl http://localhost:9222/json/version
curl http://localhost:9222/json/version
curl http://localhost:9222/json/version
{ "Browser": "Chrome/131.0.6778.86", "Protocol-Version": "1.3", "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}
{ "Browser": "Chrome/131.0.6778.86", "Protocol-Version": "1.3", "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}
{ "Browser": "Chrome/131.0.6778.86", "Protocol-Version": "1.3", "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}
mkdir browser-cli && cd browser-cli
npm init -y
npm install chrome-remote-interface commander
npm install -D typescript @types/node tsx
npx tsc --init --target ES2022 --module Node16 --moduleResolution Node16
mkdir browser-cli && cd browser-cli
npm init -y
npm install chrome-remote-interface commander
npm install -D typescript @types/node tsx
npx tsc --init --target ES2022 --module Node16 --moduleResolution Node16
mkdir browser-cli && cd browser-cli
npm init -y
npm install chrome-remote-interface commander
npm install -D typescript @types/node tsx
npx tsc --init --target ES2022 --module Node16 --moduleResolution Node16
import CDP from "chrome-remote-interface"; export interface TabInfo { id: string; title: string; url: string;
} export async function listTabs(port = 9222): Promise<TabInfo[]> { const targets = await CDP.List({ port }); return targets .filter((t) => t.type === "page") .map((t) => ({ id: t.id, title: t.title, url: t.url, }));
} export async function connectToTab( tabId: string, port = 9222
): Promise<CDP.Client> { const client = await CDP({ target: tabId, port }); await client.Runtime.enable(); await client.Network.enable(); await client.DOM.enable(); return client;
} export async function evaluateOnPage<T>( client: CDP.Client, expression: string
): Promise<T> { const { result, exceptionDetails } = await client.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true, }); if (exceptionDetails) { throw new Error( `Page evaluation failed: ${exceptionDetails.text}\n` + `${exceptionDetails.exception?.description ?? ""}` ); } return result.value as T;
}
import CDP from "chrome-remote-interface"; export interface TabInfo { id: string; title: string; url: string;
} export async function listTabs(port = 9222): Promise<TabInfo[]> { const targets = await CDP.List({ port }); return targets .filter((t) => t.type === "page") .map((t) => ({ id: t.id, title: t.title, url: t.url, }));
} export async function connectToTab( tabId: string, port = 9222
): Promise<CDP.Client> { const client = await CDP({ target: tabId, port }); await client.Runtime.enable(); await client.Network.enable(); await client.DOM.enable(); return client;
} export async function evaluateOnPage<T>( client: CDP.Client, expression: string
): Promise<T> { const { result, exceptionDetails } = await client.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true, }); if (exceptionDetails) { throw new Error( `Page evaluation failed: ${exceptionDetails.text}\n` + `${exceptionDetails.exception?.description ?? ""}` ); } return result.value as T;
}
import CDP from "chrome-remote-interface"; export interface TabInfo { id: string; title: string; url: string;
} export async function listTabs(port = 9222): Promise<TabInfo[]> { const targets = await CDP.List({ port }); return targets .filter((t) => t.type === "page") .map((t) => ({ id: t.id, title: t.title, url: t.url, }));
} export async function connectToTab( tabId: string, port = 9222
): Promise<CDP.Client> { const client = await CDP({ target: tabId, port }); await client.Runtime.enable(); await client.Network.enable(); await client.DOM.enable(); return client;
} export async function evaluateOnPage<T>( client: CDP.Client, expression: string
): Promise<T> { const { result, exceptionDetails } = await client.Runtime.evaluate({ expression, returnByValue: true, awaitPromise: true, }); if (exceptionDetails) { throw new Error( `Page evaluation failed: ${exceptionDetails.text}\n` + `${exceptionDetails.exception?.description ?? ""}` ); } return result.value as T;
}
import { program } from "commander";
import { listTabs, connectToTab, evaluateOnPage } from "./browser.js"; program .name("browser-cli") .description("Control your browser from the terminal") .version("1.0.0"); program .command("tabs") .description("List all open browser tabs") .action(async () => { const tabs = await listTabs(); tabs.forEach((tab, i) => { console.log(`[${i}] ${tab.title}`); console.log(` ${tab.url}`); console.log(` id: ${tab.id}`); }); }); program .command("eval <tabIndex> <script>") .description("Evaluate JavaScript in a tab") .action(async (tabIndex: string, script: string) => { const tabs = await listTabs(); const tab = tabs[parseInt(tabIndex, 10)]; if (!tab) { console.error(`No tab at index ${tabIndex}`); process.exit(1); } const client = await connectToTab(tab.id); try { const result = await evaluateOnPage(client, script); console.log(JSON.stringify(result, null, 2)); } finally { await client.close(); } }); program .command("extract <tabIndex>") .description("Extract structured page data from a tab") .option("--selector <css>", "CSS selector to extract text from") .option("--attr <name>", "Attribute to extract instead of textContent") .option("--json", "Output as JSON array") .action(async (tabIndex: string, opts) => { const tabs = await listTabs(); const tab = tabs[parseInt(tabIndex, 10)]; if (!tab) { console.error(`No tab at index ${tabIndex}`); process.exit(1); } const client = await connectToTab(tab.id); try { const selector = opts.selector || "h1, h2, h3, p"; const attr = opts.attr || null; const data = await evaluateOnPage<string[]>( client, `Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .map(el => ${attr ? `el.getAttribute(${JSON.stringify(attr)})` : "el.textContent.trim()"}) .filter(Boolean)` ); if (opts.json) { console.log(JSON.stringify(data, null, 2)); } else { data.forEach((item) => console.log(item)); } } finally { await client.close(); } }); program.parse();
import { program } from "commander";
import { listTabs, connectToTab, evaluateOnPage } from "./browser.js"; program .name("browser-cli") .description("Control your browser from the terminal") .version("1.0.0"); program .command("tabs") .description("List all open browser tabs") .action(async () => { const tabs = await listTabs(); tabs.forEach((tab, i) => { console.log(`[${i}] ${tab.title}`); console.log(` ${tab.url}`); console.log(` id: ${tab.id}`); }); }); program .command("eval <tabIndex> <script>") .description("Evaluate JavaScript in a tab") .action(async (tabIndex: string, script: string) => { const tabs = await listTabs(); const tab = tabs[parseInt(tabIndex, 10)]; if (!tab) { console.error(`No tab at index ${tabIndex}`); process.exit(1); } const client = await connectToTab(tab.id); try { const result = await evaluateOnPage(client, script); console.log(JSON.stringify(result, null, 2)); } finally { await client.close(); } }); program .command("extract <tabIndex>") .description("Extract structured page data from a tab") .option("--selector <css>", "CSS selector to extract text from") .option("--attr <name>", "Attribute to extract instead of textContent") .option("--json", "Output as JSON array") .action(async (tabIndex: string, opts) => { const tabs = await listTabs(); const tab = tabs[parseInt(tabIndex, 10)]; if (!tab) { console.error(`No tab at index ${tabIndex}`); process.exit(1); } const client = await connectToTab(tab.id); try { const selector = opts.selector || "h1, h2, h3, p"; const attr = opts.attr || null; const data = await evaluateOnPage<string[]>( client, `Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .map(el => ${attr ? `el.getAttribute(${JSON.stringify(attr)})` : "el.textContent.trim()"}) .filter(Boolean)` ); if (opts.json) { console.log(JSON.stringify(data, null, 2)); } else { data.forEach((item) => console.log(item)); } } finally { await client.close(); } }); program.parse();
import { program } from "commander";
import { listTabs, connectToTab, evaluateOnPage } from "./browser.js"; program .name("browser-cli") .description("Control your browser from the terminal") .version("1.0.0"); program .command("tabs") .description("List all open browser tabs") .action(async () => { const tabs = await listTabs(); tabs.forEach((tab, i) => { console.log(`[${i}] ${tab.title}`); console.log(` ${tab.url}`); console.log(` id: ${tab.id}`); }); }); program .command("eval <tabIndex> <script>") .description("Evaluate JavaScript in a tab") .action(async (tabIndex: string, script: string) => { const tabs = await listTabs(); const tab = tabs[parseInt(tabIndex, 10)]; if (!tab) { console.error(`No tab at index ${tabIndex}`); process.exit(1); } const client = await connectToTab(tab.id); try { const result = await evaluateOnPage(client, script); console.log(JSON.stringify(result, null, 2)); } finally { await client.close(); } }); program .command("extract <tabIndex>") .description("Extract structured page data from a tab") .option("--selector <css>", "CSS selector to extract text from") .option("--attr <name>", "Attribute to extract instead of textContent") .option("--json", "Output as JSON array") .action(async (tabIndex: string, opts) => { const tabs = await listTabs(); const tab = tabs[parseInt(tabIndex, 10)]; if (!tab) { console.error(`No tab at index ${tabIndex}`); process.exit(1); } const client = await connectToTab(tab.id); try { const selector = opts.selector || "h1, h2, h3, p"; const attr = opts.attr || null; const data = await evaluateOnPage<string[]>( client, `Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .map(el => ${attr ? `el.getAttribute(${JSON.stringify(attr)})` : "el.textContent.trim()"}) .filter(Boolean)` ); if (opts.json) { console.log(JSON.stringify(data, null, 2)); } else { data.forEach((item) => console.log(item)); } } finally { await client.close(); } }); program.parse();
{ "scripts": { "cli": "tsx src/cli.ts" }
}
{ "scripts": { "cli": "tsx src/cli.ts" }
}
{ "scripts": { "cli": "tsx src/cli.ts" }
}
# List all open tabs
npm run cli -- tabs # Get the page title from the first tab
npm run cli -- eval 0 "document.title" # Extract all links from a page
npm run cli -- extract 0 --selector "a[href]" --attr href --json
# List all open tabs
npm run cli -- tabs # Get the page title from the first tab
npm run cli -- eval 0 "document.title" # Extract all links from a page
npm run cli -- extract 0 --selector "a[href]" --attr href --json
# List all open tabs
npm run cli -- tabs # Get the page title from the first tab
npm run cli -- eval 0 "document.title" # Extract all links from a page
npm run cli -- extract 0 --selector "a[href]" --attr href --json
export async function captureRequests( client: CDP.Client, urlPattern: string, duration = 10000
): Promise<Array<{ url: string; status: number; body: string }>> { const captured: Array<{ url: string; status: number; body: string }> = []; const pending = new Map<string, { url: string; status: number }>(); client.on("Network.responseReceived", (params) => { if (params.response.url.includes(urlPattern)) { pending.set(params.requestId, { url: params.response.url, status: params.response.status, }); } }); client.on("Network.loadingFinished", async (params) => { const meta = pending.get(params.requestId); if (!meta) return; try { const { body } = await client.Network.getResponseBody({ requestId: params.requestId, }); captured.push({ ...meta, body }); } catch { // Response body may not be available for all requests } pending.delete(params.requestId); }); // Wait for the specified duration to collect responses await new Promise((resolve) => setTimeout(resolve, duration)); return captured;
}
export async function captureRequests( client: CDP.Client, urlPattern: string, duration = 10000
): Promise<Array<{ url: string; status: number; body: string }>> { const captured: Array<{ url: string; status: number; body: string }> = []; const pending = new Map<string, { url: string; status: number }>(); client.on("Network.responseReceived", (params) => { if (params.response.url.includes(urlPattern)) { pending.set(params.requestId, { url: params.response.url, status: params.response.status, }); } }); client.on("Network.loadingFinished", async (params) => { const meta = pending.get(params.requestId); if (!meta) return; try { const { body } = await client.Network.getResponseBody({ requestId: params.requestId, }); captured.push({ ...meta, body }); } catch { // Response body may not be available for all requests } pending.delete(params.requestId); }); // Wait for the specified duration to collect responses await new Promise((resolve) => setTimeout(resolve, duration)); return captured;
}
export async function captureRequests( client: CDP.Client, urlPattern: string, duration = 10000
): Promise<Array<{ url: string; status: number; body: string }>> { const captured: Array<{ url: string; status: number; body: string }> = []; const pending = new Map<string, { url: string; status: number }>(); client.on("Network.responseReceived", (params) => { if (params.response.url.includes(urlPattern)) { pending.set(params.requestId, { url: params.response.url, status: params.response.status, }); } }); client.on("Network.loadingFinished", async (params) => { const meta = pending.get(params.requestId); if (!meta) return; try { const { body } = await client.Network.getResponseBody({ requestId: params.requestId, }); captured.push({ ...meta, body }); } catch { // Response body may not be available for all requests } pending.delete(params.requestId); }); // Wait for the specified duration to collect responses await new Promise((resolve) => setTimeout(resolve, duration)); return captured;
}
# Capture all XHR responses matching "api" for 5 seconds
npm run cli -- capture 0 --pattern "api" --duration 5000
# Capture all XHR responses matching "api" for 5 seconds
npm run cli -- capture 0 --pattern "api" --duration 5000
# Capture all XHR responses matching "api" for 5 seconds
npm run cli -- capture 0 --pattern "api" --duration 5000
export async function extractCookies( client: CDP.Client, domain?: string
): Promise<Array<{ name: string; value: string; domain: string }>> { const { cookies } = await client.Network.getCookies(); const filtered = domain ? cookies.filter((c) => c.domain.includes(domain)) : cookies; return filtered.map((c) => ({ name: c.name, value: c.value, domain: c.domain, }));
}
export async function extractCookies( client: CDP.Client, domain?: string
): Promise<Array<{ name: string; value: string; domain: string }>> { const { cookies } = await client.Network.getCookies(); const filtered = domain ? cookies.filter((c) => c.domain.includes(domain)) : cookies; return filtered.map((c) => ({ name: c.name, value: c.value, domain: c.domain, }));
}
export async function extractCookies( client: CDP.Client, domain?: string
): Promise<Array<{ name: string; value: string; domain: string }>> { const { cookies } = await client.Network.getCookies(); const filtered = domain ? cookies.filter((c) => c.domain.includes(domain)) : cookies; return filtered.map((c) => ({ name: c.name, value: c.value, domain: c.domain, }));
}
# Extract session cookies and use them in a curl request
COOKIES=$(npm run cli -- cookies 0 --domain "github.com" --format curl)
curl -b "$COOKIES" https://api.github.com/user
# Extract session cookies and use them in a curl request
COOKIES=$(npm run cli -- cookies 0 --domain "github.com" --format curl)
curl -b "$COOKIES" https://api.github.com/user
# Extract session cookies and use them in a curl request
COOKIES=$(npm run cli -- cookies 0 --domain "github.com" --format curl)
curl -b "$COOKIES" https://api.github.com/user
export async function injectHeaders( client: CDP.Client, headers: Record<string, string>
): Promise<void> { await client.Network.setExtraHTTPHeaders({ headers });
}
export async function injectHeaders( client: CDP.Client, headers: Record<string, string>
): Promise<void> { await client.Network.setExtraHTTPHeaders({ headers });
}
export async function injectHeaders( client: CDP.Client, headers: Record<string, string>
): Promise<void> { await client.Network.setExtraHTTPHeaders({ headers });
}
export async function waitForSelector( client: CDP.Client, selector: string, timeout = 10000
): Promise<boolean> { const poll = `new Promise((resolve, reject) => { const interval = setInterval(() => { if (document.querySelector(${JSON.stringify(selector)})) { clearInterval(interval); resolve(true); } }, 100); setTimeout(() => { clearInterval(interval); reject(new Error("Timeout waiting for ${selector}")); }, ${timeout}); })`; return evaluateOnPage<boolean>(client, poll);
}
export async function waitForSelector( client: CDP.Client, selector: string, timeout = 10000
): Promise<boolean> { const poll = `new Promise((resolve, reject) => { const interval = setInterval(() => { if (document.querySelector(${JSON.stringify(selector)})) { clearInterval(interval); resolve(true); } }, 100); setTimeout(() => { clearInterval(interval); reject(new Error("Timeout waiting for ${selector}")); }, ${timeout}); })`; return evaluateOnPage<boolean>(client, poll);
}
export async function waitForSelector( client: CDP.Client, selector: string, timeout = 10000
): Promise<boolean> { const poll = `new Promise((resolve, reject) => { const interval = setInterval(() => { if (document.querySelector(${JSON.stringify(selector)})) { clearInterval(interval); resolve(true); } }, 100); setTimeout(() => { clearInterval(interval); reject(new Error("Timeout waiting for ${selector}")); }, ${timeout}); })`; return evaluateOnPage<boolean>(client, poll);
}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { listTabs, connectToTab, evaluateOnPage } from "./browser.js"; const server = new McpServer({ name: "browser-bridge", version: "1.0.0",
}); server.tool("list_tabs", "List open browser tabs", {}, async () => { const tabs = await listTabs(); return { content: [{ type: "text", text: JSON.stringify(tabs, null, 2) }] };
}); server.tool( "evaluate", "Run JavaScript in a browser tab", { tabIndex: z.number().describe("Index of the tab"), script: z.string().describe("JavaScript to evaluate"), }, async ({ tabIndex, script }) => { const tabs = await listTabs(); const client = await connectToTab(tabs[tabIndex].id); try { const result = await evaluateOnPage(client, script); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } finally { await client.close(); } }
); server.tool( "extract_content", "Extract text content from elements matching a CSS selector", { tabIndex: z.number().describe("Index of the tab"), selector: z.string().describe("CSS selector"), }, async ({ tabIndex, selector }) => { const tabs = await listTabs(); const client = await connectToTab(tabs[tabIndex].id); try { const data = await evaluateOnPage<string[]>( client, `Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .map(el => el.textContent.trim()).filter(Boolean)` ); return { content: [{ type: "text", text: data.join("\n") }] }; } finally { await client.close(); } }
); const transport = new StdioServerTransport();
await server.connect(transport);
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { listTabs, connectToTab, evaluateOnPage } from "./browser.js"; const server = new McpServer({ name: "browser-bridge", version: "1.0.0",
}); server.tool("list_tabs", "List open browser tabs", {}, async () => { const tabs = await listTabs(); return { content: [{ type: "text", text: JSON.stringify(tabs, null, 2) }] };
}); server.tool( "evaluate", "Run JavaScript in a browser tab", { tabIndex: z.number().describe("Index of the tab"), script: z.string().describe("JavaScript to evaluate"), }, async ({ tabIndex, script }) => { const tabs = await listTabs(); const client = await connectToTab(tabs[tabIndex].id); try { const result = await evaluateOnPage(client, script); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } finally { await client.close(); } }
); server.tool( "extract_content", "Extract text content from elements matching a CSS selector", { tabIndex: z.number().describe("Index of the tab"), selector: z.string().describe("CSS selector"), }, async ({ tabIndex, selector }) => { const tabs = await listTabs(); const client = await connectToTab(tabs[tabIndex].id); try { const data = await evaluateOnPage<string[]>( client, `Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .map(el => el.textContent.trim()).filter(Boolean)` ); return { content: [{ type: "text", text: data.join("\n") }] }; } finally { await client.close(); } }
); const transport = new StdioServerTransport();
await server.connect(transport);
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { listTabs, connectToTab, evaluateOnPage } from "./browser.js"; const server = new McpServer({ name: "browser-bridge", version: "1.0.0",
}); server.tool("list_tabs", "List open browser tabs", {}, async () => { const tabs = await listTabs(); return { content: [{ type: "text", text: JSON.stringify(tabs, null, 2) }] };
}); server.tool( "evaluate", "Run JavaScript in a browser tab", { tabIndex: z.number().describe("Index of the tab"), script: z.string().describe("JavaScript to evaluate"), }, async ({ tabIndex, script }) => { const tabs = await listTabs(); const client = await connectToTab(tabs[tabIndex].id); try { const result = await evaluateOnPage(client, script); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } finally { await client.close(); } }
); server.tool( "extract_content", "Extract text content from elements matching a CSS selector", { tabIndex: z.number().describe("Index of the tab"), selector: z.string().describe("CSS selector"), }, async ({ tabIndex, selector }) => { const tabs = await listTabs(); const client = await connectToTab(tabs[tabIndex].id); try { const data = await evaluateOnPage<string[]>( client, `Array.from(document.querySelectorAll(${JSON.stringify(selector)})) .map(el => el.textContent.trim()).filter(Boolean)` ); return { content: [{ type: "text", text: data.join("\n") }] }; } finally { await client.close(); } }
); const transport = new StdioServerTransport();
await server.connect(transport); - Authentication is hard. OAuth flows, MFA, CAPTCHAs, and bot detection all fight against automated logins.
- The page you see isn't the page your bot sees. Extensions, feature flags tied to accounts, and personalized content all differ. - Internal tools behind SSO where programmatic auth is impossible
- Dashboards that require human login but repetitive data extraction
- AI-assisted workflows where an LLM needs to read or act on page content
- Developer tooling that augments your browser with terminal superpowers - listTabs queries the CDP HTTP endpoint for all open page targets.
- connectToTab opens a WebSocket connection to a specific tab and enables the domains we need.
- evaluateOnPage runs arbitrary JavaScript in the context of the connected page and returns the result. - Bind to localhost only. Chrome does this by default, but never use --remote-debugging-address=0.0.0.0.
- Close the debugging port when not in use. Restart Chrome normally when you are done.
- Be careful with cookie extraction. Session tokens in logs or shell history are a security risk. Pipe them directly rather than echoing to the terminal.
- Do not run untrusted scripts. The eval command executes arbitrary JavaScript with full page privileges. - Launch Chrome with --remote-debugging-port=9222 to enable CDP access.
- Use chrome-remote-interface to connect, evaluate scripts, and intercept network traffic.
- Reusing a live session means zero authentication code for any site you are logged into.
- The MCP bridge pattern turns your CLI into an AI-accessible tool, enabling LLM-driven browser automation.
- Network interception often yields cleaner data than DOM scraping, since you capture the raw API responses.