omising credentials or triggering security blocks.
Core Solution
The solution requires a local MCP server that bridges the AI agent to the user's existing browser profile. This server must reuse the active session, expose tools via the MCP protocol, and ensure no sensitive data leaves the local environment.
Architecture Decisions
- Local Execution: The MCP server runs as a local process. Communication between the agent and the server uses
stdio transport, ensuring traffic never traverses the public internet.
- Profile Reuse: Instead of spawning a new browser, the server connects to an existing Chrome instance via Chrome DevTools Protocol (CDP) or launches a browser with a specific user data directory. This preserves cookies, local storage, and IndexedDB.
- Session Validation: The server must validate session health before executing actions. If a session has expired, the server should return a structured error prompting the user to re-authenticate manually, rather than attempting to bypass security.
- Security Boundaries: The bridge should redact sensitive data in logs and limit the scope of tools to prevent unauthorized data exfiltration.
Implementation Example
The following TypeScript example demonstrates a LocalBrowserMCP server. This implementation connects to a local Chrome instance via CDP, validates the session, and exposes a tool to extract data from an authenticated dashboard.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import puppeteer from "puppeteer-core";
// Configuration for local browser connection
interface LocalBrowserConfig {
cdpEndpoint: string;
allowedDomains: string[];
sessionValidationUrl: string;
}
export class AuthenticatedBrowserBridge {
private server: McpServer;
private config: LocalBrowserConfig;
private browser: puppeteer.Browser | null = null;
constructor(config: LocalBrowserConfig) {
this.config = config;
this.server = new McpServer({
name: "local-auth-browser",
version: "1.0.0",
});
this.registerTools();
}
private registerTools(): void {
// Tool to extract data from an authenticated resource
this.server.tool(
"extract_authenticated_data",
"Extracts data from a URL requiring authentication. Uses the local browser session.",
{
url: z.string().url().describe("Target URL within allowed domains"),
selector: z.string().describe("CSS selector for data extraction"),
maxRetries: z.number().optional().default(3),
},
async ({ url, selector, maxRetries }) => {
try {
await this.ensureBrowser();
await this.validateSession();
const page = await this.browser!.newPage();
await page.goto(url, { waitUntil: "networkidle0" });
// Wait for selector with timeout
await page.waitForSelector(selector, { timeout: 10000 });
const data = await page.evaluate((sel) => {
const elements = document.querySelectorAll(sel);
return Array.from(elements).map((el) => el.textContent?.trim());
}, selector);
await page.close();
return {
content: [{ type: "text", text: JSON.stringify(data) }],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
}
private async ensureBrowser(): Promise<void> {
if (this.browser) return;
try {
this.browser = await puppeteer.connect({
browserURL: this.config.cdpEndpoint,
defaultViewport: null,
});
} catch (error) {
throw new Error(
"Failed to connect to local browser. Ensure Chrome is running with --remote-debugging-port."
);
}
}
private async validateSession(): Promise<void> {
const page = await this.browser!.newPage();
await page.goto(this.config.sessionValidationUrl, {
waitUntil: "networkidle0",
});
const url = page.url();
await page.close();
// Check if redirected to login page
if (url.includes("/login") || url.includes("/auth")) {
throw new Error(
"Session expired or invalid. Please re-authenticate in your browser and retry."
);
}
}
async start(): Promise<void> {
const transport = new StdioTransport();
await this.server.connect(transport);
console.error("Local Authenticated Browser MCP started.");
}
}
// Usage
const config: LocalBrowserConfig = {
cdpEndpoint: "http://127.0.0.1:9222",
allowedDomains: ["admin.shopify.com", "app.hubspot.com"],
sessionValidationUrl: "https://admin.shopify.com/api/2023-01/shop.json",
};
const bridge = new AuthenticatedBrowserBridge(config);
bridge.start().catch(console.error);
Rationale
- Puppeteer-core vs. Puppeteer: Using
puppeteer-core avoids downloading a bundled Chromium. This ensures the server connects to the user's actual Chrome installation, preserving the exact fingerprint and session state.
- Session Validation: The
validateSession method checks for redirection to login pages. This prevents the agent from wasting tokens on failed requests and provides clear feedback to the user.
- Allowed Domains: Restricting tools to specific domains mitigates the risk of the agent accessing unintended resources.
- Stdio Transport: This keeps all communication local. The agent process and the MCP server communicate via standard streams, eliminating network exposure.
Pitfall Guide
Implementing local-first browser automation introduces unique challenges. The following pitfalls are common in production environments.
-
Profile Contamination
- Explanation: Reusing the user's default profile may expose the agent to extensions, bookmarks, or state that interferes with automation. Extensions like ad blockers or password managers can alter DOM structures or block requests.
- Fix: Use a dedicated Chrome profile for automation. Launch Chrome with
--user-data-dir pointing to a clean directory, or use a separate profile slot. Disable unnecessary extensions in that profile.
-
CDP Security Exposure
- Explanation: Chrome DevTools Protocol provides full control over the browser. If the CDP endpoint is exposed to the network, malicious actors could hijack the session.
- Fix: Always bind the CDP endpoint to
127.0.0.1. Never expose CDP ports to 0.0.0.0. Use firewall rules to restrict access to localhost only.
-
Session Drift and Stale State
- Explanation: Sessions may expire during long-running agent tasks. The agent might attempt actions with an expired token, leading to inconsistent results.
- Fix: Implement periodic session health checks. Tools should validate session state before critical operations. Design workflows to handle
SessionExpired errors gracefully, prompting the user to refresh the session.
-
Extension Interference
- Explanation: Browser extensions can inject scripts or modify network requests, causing selectors to fail or data to be corrupted.
- Fix: Run automation in a profile with extensions disabled. If extensions are required, test thoroughly and add robust error handling for DOM variations caused by extension injection.
-
2FA Deadlocks
- Explanation: If a session requires re-authentication with 2FA, the agent cannot proceed. The workflow hangs waiting for a response that will never come.
- Fix: Detect 2FA challenges (e.g., specific URLs or DOM elements). Return a structured error to the agent indicating that human intervention is required. The agent can then notify the user to approve the login manually.
-
Resource Leaks
- Explanation: Opening multiple pages or contexts without closing them can exhaust browser resources, leading to crashes or performance degradation.
- Fix: Ensure every
page.open() has a corresponding page.close(). Use try/finally blocks to guarantee cleanup. Monitor browser memory usage in long-running sessions.
-
Credential Leakage in Logs
- Explanation: Debug logs may inadvertently capture cookies, tokens, or sensitive page content.
- Fix: Implement log redaction. Filter out headers containing
Authorization, Cookie, or Set-Cookie. Avoid logging page content that may contain PII or secrets. Use structured logging with sensitive fields masked.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Public Web Scraping | Cloud MCP / Crawler | No auth required. Cloud offers scalability and IP rotation. | Low (Cloud compute costs) |
| Internal Dashboard Analysis | Local MCP Bridge | Requires session state. Local execution preserves auth and privacy. | Low (Local compute) |
| High-Frequency API Calls | Direct API Integration | Browser automation is slow. Use APIs where available. | Medium (API rate limits) |
| Legacy Web Apps (No API) | Local MCP Bridge | Only browser automation can interact with legacy UIs. Local preserves session. | Low (Local compute) |
| Multi-Account Workflows | Local MCP with Profile Switching | Requires managing multiple sessions. Local profiles allow isolation. | Medium (Complexity) |
Configuration Template
Use this template to configure your MCP client to connect to the local authenticated browser server.
{
"mcpServers": {
"local-auth-browser": {
"command": "node",
"args": ["path/to/your/authenticated-browser-bridge.js"],
"env": {
"CDP_ENDPOINT": "http://127.0.0.1:9222",
"SESSION_VALIDATION_URL": "https://admin.shopify.com/api/2023-01/shop.json",
"ALLOWED_DOMAINS": "admin.shopify.com,app.hubspot.com"
},
"transport": "stdio"
}
}
}
Quick Start Guide
- Launch Chrome with Debugging: Start Chrome with remote debugging enabled:
open -a "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir=/tmp/automation-profile
- Deploy Bridge: Install the
AuthenticatedBrowserBridge package and configure the mcpServers JSON in your agent configuration.
- Verify Session: Open the target application (e.g., Shopify) in the launched Chrome profile and ensure you are logged in.
- Test Tool: Instruct your agent to use the
extract_authenticated_data tool. Verify that the agent can access the authenticated content without login prompts.
- Monitor: Check agent logs for session validation results and ensure no sensitive data is exposed.
By adopting a local-first architecture for authenticated browser automation, teams can unlock the full potential of AI agents for internal workflows while maintaining strict security and compliance standards. This approach bridges the gap between agent capabilities and the reality of modern web authentication.