WebSocket Connection Flow for MCP to Chrome with Custom Headers: A Complete Technical Guide

The WebSocket connection flow routes custom headers from CLI arguments through src/cli.ts, src/main.ts, and src/browser.ts before Puppeteer injects them into the HTTP upgrade handshake with Chrome's DevTools Protocol endpoint.

The chrome-devtools-mcp repository enables Model Context Protocol (MCP) servers to control Chrome via the DevTools Protocol. When connecting to a remote or authenticated Chrome instance, you often need to pass custom HTTP headers—such as authorization tokens—during the WebSocket handshake. Understanding the WebSocket connection flow for MCP to Chrome with custom headers ensures you can securely authenticate and establish stable debugging sessions.

Architecture Overview

The connection pipeline consists of four distinct layers. Each layer transforms the header data before passing it to the next stage:

  1. CLI Parser (src/cli.ts) – Validates and parses the JSON header string
  2. Main Entry (src/main.ts) – Transports parsed arguments to the browser module
  3. Browser Connector (src/browser.ts) – Constructs Puppeteer connection options
  4. Puppeteer CDP – Executes the WebSocket handshake with custom headers

Step-by-Step Connection Flow

CLI Argument Parsing and Validation

The flow begins in src/cli.ts where the command-line interface defines the --wsHeaders option. Lines 68-88 implement a custom coerce function that parses the incoming JSON string into a Record<string, string> type.

If the JSON is malformed or contains non-string values, the parser throws an error immediately. The --wsHeaders flag implicitly requires --wsEndpoint, ensuring headers are only processed when a WebSocket connection is explicitly requested.

Main Entry Point Processing

Once parsed, the arguments flow into src/main.ts. Lines 90-95 export the args object containing the validated wsHeaders property. The main function passes these headers directly to the browser connection helper without transformation or validation, acting as a transparent transport layer.

This design keeps the main entry point agnostic of connection specifics while ensuring the header data reaches the browser module intact.

Browser Connection Setup

The critical transformation occurs in src/browser.ts within the ensureBrowserConnected function. Around line 48, the function signature accepts an options object containing the wsHeaders property.

Lines 63-69 construct the Puppeteer ConnectOptions object. When wsHeaders is present, the code attaches it directly to connectOptions.headers. This is the final preparation step before the WebSocket handshake occurs.

Puppeteer WebSocket Handshake

At line 20, the code invokes puppeteer.connect(connectOptions). Puppeteer's internal CDPConnection implementation uses the supplied headers when performing the HTTP upgrade request to the Chrome remote-debugging WebSocket endpoint specified by wsEndpoint.

The custom headers—such as Authorization: Bearer <token>—are transmitted during the handshake phase, allowing Chrome or intermediate proxies to authenticate the connection before the DevTools Protocol session begins.

DevTools Adapter Initialization

Once the WebSocket connection succeeds, src/DevToolsConnectionAdapter.ts wraps the raw Puppeteer session in a PuppeteerDevToolsConnection instance. This adapter exposes the standard Chrome DevTools Protocol interface to the rest of the MCP toolset.

No further handling of the custom headers occurs at this stage; the connection is established and authenticated, and the MCP server proceeds with standard CDP commands.

Practical Implementation Examples

Connecting via CLI with Authentication

The most common use case involves connecting to a Chrome instance behind an authentication proxy:

npx chrome-devtools-mcp@latest \
  --wsEndpoint ws://127.0.0.1:9222/devtools/browser/abc123 \
  --wsHeaders '{"Authorization":"Bearer my-token","X-Custom-Id":"session-456"}'

The JSON string must contain only string keys and values. The CLI parser validates this before attempting the connection.

Programmatic Connection in Node.js

For custom MCP server implementations, import the browser connector directly:

import { ensureBrowserConnected } from './src/browser.js';

const browser = await ensureBrowserConnected({
  wsEndpoint: 'ws://127.0.0.1:9222/devtools/browser/abc123',
  wsHeaders: {
    Authorization: 'Bearer my-token',
    'X-Request-Source': 'mcp-server'
  },
  devtools: false,
});

// browser is now a connected Puppeteer Browser instance

Header Injection Point

The actual attachment occurs in src/browser.ts before Puppeteer consumes the options:

// From src/browser.ts, lines 63-69
const connectOptions: puppeteer.ConnectOptions = {
  browserWSEndpoint: options.wsEndpoint,
};

if (options.wsHeaders) {
  connectOptions.headers = options.wsHeaders; // Headers injected here
}

browser = await puppeteer.connect(connectOptions);

Key Files in the Connection Pipeline

File Responsibility Critical Lines
src/cli.ts Defines --wsHeaders flag, parses JSON validation 68-88
src/main.ts Transports parsed arguments to browser module 90-95
src/browser.ts Constructs Puppeteer options, attaches headers, initiates handshake 48, 63-69, 20
src/DevToolsConnectionAdapter.ts Wraps established connection for MCP tooling Full file

Summary

  • The WebSocket connection flow for MCP to Chrome with custom headers begins with CLI argument parsing in src/cli.ts, which validates the JSON header format.
  • The src/main.ts entry point acts as a transparent transport layer, passing headers to the browser connection helper without modification.
  • In src/browser.ts, the ensureBrowserConnected function attaches custom headers to Puppeteer's connectOptions.headers before calling puppeteer.connect().
  • Puppeteer injects these headers during the HTTP upgrade handshake to Chrome's DevTools Protocol WebSocket endpoint, enabling authentication and custom routing.
  • Once established, the connection is wrapped by src/DevToolsConnectionAdapter.ts for standard MCP tooling access.

Frequently Asked Questions

How do I format the --wsHeaders JSON string for the CLI?

The --wsHeaders flag expects a JSON object with string keys and string values. Use single quotes around the entire argument to prevent shell escaping issues, and double quotes inside the JSON. For example: '{"Authorization":"Bearer token123","X-Custom":"value"}'. The parser in src/cli.ts validates that all values are strings and rejects malformed JSON.

Can I use custom headers when connecting to a local Chrome instance without authentication?

Yes, you can supply custom headers to any WebSocket endpoint, but they are typically only necessary when Chrome runs behind a reverse proxy or requires authentication. If connecting to localhost:9222 without security restrictions, you can omit --wsHeaders entirely. The connection flow supports headers optionally; they are only injected when explicitly provided.

What happens if the custom headers cause the WebSocket handshake to fail?

If Chrome or an intermediate proxy rejects the headers (for example, due to an invalid authorization token), Puppeteer's puppeteer.connect() call will throw a connection error. The error propagates up from src/browser.ts and typically manifests as a timeout or HTTP 401/403 response during the upgrade handshake. Verify your headers are correct and that the Chrome endpoint accepts them.

Is there a way to dynamically change headers after the initial connection?

No, the custom headers are only used during the initial WebSocket handshake in src/browser.ts. Once puppeteer.connect() establishes the session, the connection is maintained over the WebSocket protocol, which does not support mid-stream header modification. If you need to change authentication credentials, you must close the existing connection and create a new one with updated headers via the ensureBrowserConnected function.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →