← Blog

BLOG

Playwright MCP Connect to Existing Browser: A Practical Guide

Learn how Playwright MCP connects to an existing browser via CDP, when to use it, and how to point it at a hosted Chromium session in production.

September 16, 20268 min readRemote Browser

# Playwright MCP Connect to Existing Browser: A Practical Guide

If you want Playwright MCP to connect to an existing browser instead of launching its own, the answer is CDP. The Playwright MCP server drives a browser over the Chrome DevTools Protocol, and browserType.connectOverCDP() is the entry point that lets it attach to a Chrome or Chromium instance that is already running — locally, on another host, or inside a hosted runtime. This guide covers how the connection actually works, what breaks in practice, and how to point MCP at a remote Chromium session you don't have to babysit.

What "connect to an existing browser" means for Playwright MCP

Playwright MCP is a Model Context Protocol server that exposes browser actions — navigate, click, type, snapshot — as tools an LLM can call. By default it launches its own Chromium process. That default is fine for a laptop demo and wrong for almost everything else: the browser dies when the process exits, the profile is throwaway, and you can't see what the agent is doing.

Connecting to an existing browser changes the ownership model. Instead of MCP spawning Chromium, you give it a WebSocket endpoint that a running browser is already listening on. MCP attaches, takes over the page context, and drives it. The browser keeps running whether or not MCP is alive.

There are two ways to get that endpoint:

  • Launch Chrome yourself with `--remote-debugging-port`. You control the flags, the profile directory, and the lifecycle.
  • Use a hosted runtime that exposes a CDP endpoint. You get a URL, paste it into your config, and the runtime handles process management, isolation, and cleanup.

The second option is what most production agent stacks converge on, because the first one means you're now operating Chrome fleets.

How the CDP handshake works

When Playwright calls connectOverCDP(), it opens a WebSocket to the browser's DevTools endpoint, typically ws://host:port/devtools/browser/<id>. Over that socket it speaks the Chrome DevTools Protocol — the same protocol Chrome DevTools itself uses. Playwright then wraps the connection in its normal Browser object, so browser.contexts(), page.goto(), and locators all work as usual.

Two details matter and are easy to get wrong:

  1. `connectOverCDP` is Chromium-only. Firefox and WebKit do not implement the CDP surface Playwright needs here. If your MCP config points at a Firefox endpoint, the connection will fail or behave unpredictably. Use Chromium.
  2. You attach to existing contexts, not new ones. With connectOverCDP, browser.newContext() is not available in the way it is for a launched browser. You work with browser.contexts()[0] or create pages on the default context. Code that assumes a fresh context per task will need adjusting.

The official Playwright reference for this method is browserType.connectOverCDP() in the Playwright API docs.

Minimal TypeScript example

Here is the shape of a connection you can adapt for an MCP server or a standalone script. It assumes you already have a CDP WebSocket URL.

import { chromium, Browser, BrowserContext, Page } from 'playwright';

const CDP_ENDPOINT = process.env.CDP_WS_ENDPOINT!; // ws://.../devtools/browser/<id>

async function attach(): Promise<{ browser: Browser; context: BrowserContext; page: Page }> {
  const browser = await chromium.connectOverCDP(CDP_ENDPOINT, {
    timeout: 30_000,
  });

  // connectOverCDP attaches to existing contexts; do not call newContext().
  const context = browser.contexts()[0];
  if (!context) {
    throw new Error('No browser context available on the CDP endpoint');
  }

  const page = context.pages()[0] ?? (await context.newPage());

  // Confirm the connection is live before handing it to an agent.
  await page.goto('about:blank');
  return { browser, context, page };
}

async function main() {
  const { browser, page } = await attach();
  await page.goto('https://example.com');
  console.log(await page.title());

  // Close the Playwright connection, not the remote browser.
  await browser.close();
}

main().catch((err) => {
  console.error('CDP attach failed:', err);
  process.exit(1);
});

The important line is browser.close(). Over CDP, that closes the Playwright connection. Whether the underlying browser process terminates depends on how it was started — a hosted runtime typically keeps the session alive until you explicitly release it, which is what you want when a second tool or a human viewer needs to stay attached.

Configuring Playwright MCP to use an existing browser

Playwright MCP accepts a CDP endpoint through its configuration. The exact flag name depends on your MCP client and server version, but the pattern is consistent: you supply a WebSocket URL instead of letting the server launch Chromium. A typical config block looks like this:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest",
        "--cdp-endpoint",
        "wss://your-runtime.example/devtools/browser/abc123"
      ]
    }
  }
}

Check the current flag against your installed server version before you commit it to a repo — MCP server options have moved around, and a stale flag fails silently in some clients. If you're running against a hosted runtime, the endpoint is usually handed to you per session, so the config is generated at runtime rather than hardcoded.

Local Chrome vs hosted Chromium for MCP

The decision is not really about Playwright. It's about who owns the browser process.

CriterionLocal Chrome + --remote-debugging-portHosted Chromium runtime
SetupManual flags, profile dir, port managementPaste a CDP URL
LifecycleDies with your shell or machineSurvives client restarts
IsolationShared profile, shared cookiesSession-scoped, isolated
ConcurrencyOne browser, one port, contentionMultiple sessions, separate endpoints
ObservabilityDevTools on localhost onlyLive viewer, remote access
Proxies / networkYour machine's IPConfigurable per session
ScalingYou operate the fleetRuntime operates the fleet
DebuggingAttach DevTools locallyAttach DevTools or use the viewer

Local Chrome is the right call when you're debugging a selector or reproducing a bug on your own machine. It stops being the right call the moment the agent needs to run unattended, run more than one session at a time, or run from a machine that isn't yours.

Common failure modes

Most "Playwright MCP won't connect" reports trace back to a small set of causes.

The endpoint is an HTTP URL, not a WebSocket URL. connectOverCDP wants ws:// or wss://. An http://host:9222 value will fail. Fetch http://host:9222/json/version and read the webSocketDebuggerUrl field if you need to discover it.

Chrome wasn't started with remote debugging enabled. Without --remote-debugging-port, there is no endpoint to connect to. If you're launching Chrome yourself, that flag is mandatory. If you're using a hosted runtime, the endpoint is created for you.

The browser is bound to localhost. A CDP port on 127.0.0.1 is unreachable from another container or host. Hosted runtimes solve this by exposing a routable endpoint; self-hosted setups need --remote-debugging-address=0.0.0.0 plus network controls, which is a security decision, not just a config tweak.

The connection drops mid-session. CDP WebSockets are long-lived and fragile across networks. Production code should treat a dropped connection as recoverable: reconnect, re-resolve the page, and continue. Don't assume the socket outlives the task.

You called `newContext()`. As noted above, connectOverCDP attaches to existing contexts. Code ported from a launch() flow will throw here.

Version skew. Playwright's CDP client tracks a specific protocol surface. A browser far ahead of your Playwright version can expose methods Playwright doesn't expect. Pin both sides.

Why hosted Chromium fits MCP better than local Chrome

MCP servers are usually spawned by a client — an IDE, a chat app, an agent framework — and that client restarts constantly. If the browser is a child process of the MCP server, every restart destroys the session: cookies gone, login gone, half-finished task gone.

Decoupling the browser from the MCP process fixes this. The runtime holds the Chromium session; MCP holds a connection to it. Restart the client, reconnect to the same endpoint, and the page is still where you left it. That's the same architectural argument behind remote browsers for AI agents, and it applies whether your agent is a coding assistant or a scheduled scraper.

The other practical win is the live viewer. When an agent is driving a browser over CDP, you can't see anything unless you attach DevTools. A hosted runtime that exposes a viewer lets you watch the session in a browser tab while the agent works — which turns "the agent did something weird" from a log-reading exercise into a thirty-second observation. This is the same reason remote control of a browser matters for debugging agent runs.

Production criteria before you commit

If you're choosing between self-hosted Chrome and a hosted runtime for MCP, evaluate against these:

  • Session persistence. Does the browser survive an MCP client restart? If not, you'll rebuild auth state constantly.
  • Isolation. Can two concurrent agents share a browser without leaking cookies or storage between them? They shouldn't.
  • Endpoint stability. Is the CDP URL stable for the life of a session, or does it rotate in ways your reconnect logic must handle?
  • Network controls. Can you route traffic through a specific proxy per session? Many sites behave differently by IP, and this is a per-session concern, not a global one.
  • Observability. Can a human watch the session without SSH access to the host?
  • Cleanup guarantees. When a task ends or crashes, does the session get released? Orphaned Chromium processes are the default failure mode of self-hosted setups.
  • Cost model. Browser time is metered differently across providers. Check current pricing rather than assuming a per-task or per-hour model.

None of these are Playwright problems. They're runtime problems, and they're the reason the "connect to an existing browser" pattern exists in the first place.

A reasonable default

For local development, launch Chrome with --remote-debugging-port=9222, point Playwright MCP at the discovered WebSocket URL, and iterate. You get fast feedback and full DevTools.

For anything that runs unattended, move the browser off your machine. Start a hosted Chromium session, take the CDP endpoint it returns, and configure MCP to attach. Keep the reconnect logic, keep the browser.close() semantics straight, and treat the session as a resource with a lifecycle rather than a process your script happens to own.

The connection mechanics are the same in both cases — a WebSocket, a protocol, a Browser object. What changes is who's responsible when the browser dies at 3 a.m. That's the part worth getting right before you ship.