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

> Understand the WebSocket connection flow for MCP to Chrome with custom headers. Learn how custom headers are routed and injected into the handshake.

- Repository: [ChromeDevTools/chrome-devtools-mcp](https://github.com/chromedevtools/chrome-devtools-mcp)
- Tags: how-to-guide
- Published: 2026-02-16

---

**The WebSocket connection flow routes custom headers from CLI arguments through [`src/cli.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/cli.ts), [`src/main.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/main.ts), and [`src/browser.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/cli.ts)) – Validates and parses the JSON header string
2. **Main Entry** ([`src/main.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/main.ts)) – Transports parsed arguments to the browser module
3. **Browser Connector** ([`src/browser.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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:

```bash
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:

```typescript
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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/browser.ts) before Puppeteer consumes the options:

```typescript
// 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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/cli.ts) | Defines `--wsHeaders` flag, parses JSON validation | 68-88 |
| [`src/main.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/main.ts) | Transports parsed arguments to browser module | 90-95 |
| [`src/browser.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/browser.ts) | Constructs Puppeteer options, attaches headers, initiates handshake | 48, 63-69, 20 |
| [`src/DevToolsConnectionAdapter.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/cli.ts), which validates the JSON header format.
- The [`src/main.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/main.ts) entry point acts as a transparent transport layer, passing headers to the browser connection helper without modification.
- In [`src/browser.ts`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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`](https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/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.