Vũ Văn HảiFull-stack · AI-native
GuidesBlog
Discuss a project

© 2026 Vu Van Hai · Written from real deployment experience.

HomeGuidesBlogRSS
  1. Guides
  2. /Dev tools
  3. /A dedicated Chrome for chrome-devtools-mcp that can sign in

A dedicated Chrome for chrome-devtools-mcp that can sign in

Set up a per-project Chrome profile for chrome-devtools-mcp (Claude Code, Cursor...) to drive: it can still sign in to Google/Cloudflare and keep the session, run several projects in parallel, and never touch your personal Chrome.

Updated: Sep 22, 202611 min read
Claude CodeMCPBrowser automationWindows
On this page
  • The problem with the default setup
  • Comparing the launch methods
  • Why Chrome allows the debug port on a dedicated profile
  • Why sign-in is blocked, and how to avoid it legitimately
  • How it works
  • Setting up a project
  • The code
  • Launcher open-chrome-mcp.ps1
  • The project's two double-click files
  • The chrome-devtools entry in .mcp.json
  • Day-to-day use
  • Security
  • Troubleshooting
  • References

Letting chrome-devtools-mcp (the MCP server that drives a browser for Claude Code, Cursor, VS Code...) launch Chrome itself causes three problems: Google and Cloudflare block sign-in, two projects opening a browser at once collide, and the agent can see every tab in your personal Chrome. This guide sets up a dedicated Chrome profile per project so the MCP attaches only to that browser, can still sign in and keep the session, and runs several projects in parallel.

Throughout, <profiles-root> is the folder that holds your profiles (for example %USERPROFILE%\chrome-mcp-profiles), and <project-slug> is a project's short name (lowercase, digits, hyphens). Verified on Windows 11 + Chrome stable 153 + chrome-devtools-mcp 1.9.0; the idea applies to other MCP clients too.

The problem with the default setup

When .mcp.json declares chrome-devtools-mcp with no flags, the MCP launches Chrome with Puppeteer using one profile shared across every project. Four consequences:

ProblemCause
Google blocks sign-in ("This browser or app may not be secure"), Cloudflare Turnstile fails to verifyPuppeteer adds --enable-automation, and the remote-debugging port itself sets navigator.webdriver = true - the site sees a controlled browser
Two projects opening a browser at once: The browser is already running... Use --isolatedThe default profile is shared; Chrome allows only one process per data directory
The agent sees the wrong tabs / your personal profileBare --autoConnect (with no dedicated profile) attaches to the whole main Chrome process, seeing every profile and tab
--isolated loses the login every time you close it--isolated creates a temp directory and deletes it when Chrome closes

Comparing the launch methods

The crux: when the MCP launches Chrome it always adds an automation flag (sign-in blocked); when you launch Chrome yourself and the MCP only CONNECTS, that flag is absent.

MethodGoogle/CF sign-inLogin persistsAutomation flagProjects in parallelChrome dies with MCP
Default (no flags), MCP launchesNoNo (shared profile)YesNoYes
--isolated, MCP launchesNoNo (cleared on close)YesYesYes
Dedicated --userDataDir only, MCP launchesNoYesYesNoYes
--autoConnect + dedicated --userDataDir, YOU launch ChromeYesYesNoYesNo

The last row is this guide's goal. You open Chrome with a launcher (no automation flag), and the MCP just reads that folder's debug port and attaches.

Why Chrome allows the debug port on a dedicated profile

Since Chrome 136, --remote-debugging-port and --remote-debugging-pipe are ignored on the default data directory; to use them you must add --user-data-dir pointing to a different directory. The reason: since App-Bound Encryption shipped, attackers have used remote debugging to steal cookies, so Chrome closes that path on the default profile.

Thanks to this change, your personal Chrome (the default profile) cannot have a debug port opened via a command-line flag. Only a DEDICATED data directory can - exactly what we need.

Why sign-in is blocked, and how to avoid it legitimately

Google/Cloudflare block when they see navigator.webdriver === true. That property is true whenever any control signal is present:

Flag when Chrome launchesnavigator.webdriverSign-in works?
--enable-automation (Puppeteer adds it when the MCP launches Chrome)trueNo
--headlesstrueNo
--remote-debugging-port (including =0) or --remote-debugging-pipetrueNo
None of the above (a normal Chrome)falseYes

So you cannot sign in while a debug port is open. The legitimate workaround (no spoofing tricks) is to split two modes over the same data directory:

  • Login mode: open Chrome with NO debug port and no automation flag. You sign in to Google/Cloudflare like a normal browser. Cookies are saved into the profile folder.
  • Work mode: fully close that Chrome, reopen it WITH --remote-debugging-port=0. The MCP attaches. The cookies from the login are still there, so pages keep the session - and now you do not need to sign in again.

Do not use a stealth plugin or patch navigator.webdriver to "get past" Google/Cloudflare - that is bot-detection evasion. The two-mode split above signs in with a CLEAN, uncontrolled browser session, which is entirely legitimate.

How it works

Your main Chrome                Project A's Chrome                Project B's Chrome
(default profile, no port)      <profiles-root>\A                 <profiles-root>\B
        ^                       --remote-debugging-port=0         --remote-debugging-port=0
        |                              | writes                          | writes
  no MCP attaches here          A\DevToolsActivePort (random port)       B\DevToolsActivePort
                                       ^ reads                          ^ reads
                               Project A's MCP                    Project B's MCP
                               --autoConnect                      --autoConnect
                               --userDataDir=<...>\A              --userDataDir=<...>\B

The detailed flow:

  1. The launcher runs chrome.exe --user-data-dir="<profiles-root>\<slug>" --no-first-run --no-default-browser-check --remote-debugging-port=0.
  2. Chrome picks a free port and writes <folder>\DevToolsActivePort with two lines: the port number and a /devtools/browser/<uuid> path. The port listens on 127.0.0.1 only.
  3. With --autoConnect, the MCP reads DevToolsActivePort inside the exact --userDataDir folder, builds ws://127.0.0.1:<port><path> and connects. If the file is missing or the port does not answer, the MCP reports Could not connect to Chrome in <folder> and does NOT launch a browser of its own.
  4. When the MCP session ends, the MCP only disconnects - Chrome stays open.

Setting up a project

  1. Create the root folder <profiles-root> (e.g. %USERPROFILE%\chrome-mcp-profiles) and put the launcher open-chrome-mcp.ps1 (code below) in it.
  2. Choose <project-slug> matching ^[a-z0-9][a-z0-9-]*$, one slug per project.
  3. Create the two .cmd files in the project root (code below), substituting <project-slug> and the launcher path.
  4. Edit the project's .mcp.json: point the chrome-devtools entry's --autoConnect --userDataDir at <profiles-root>\<project-slug>.
  5. Sign in (if the project needs accounts): run login mode, sign in, fully close it.
  6. Open work mode, then restart the MCP client so it loads the new .mcp.json.

The code

Launcher open-chrome-mcp.ps1

Put it in <profiles-root>. Save it as UTF-8 with BOM (PowerShell 5.1 reads a BOM-less file as ANSI, mangling non-ASCII text). The script locates its own folder, so it holds no absolute paths.

param(
    [Parameter(Mandatory = $true)]
    [ValidatePattern('^[a-z0-9][a-z0-9-]*$')]
    [string]$Project,
    [switch]$Login
)

$ErrorActionPreference = 'Stop'
$profileDir = Join-Path $PSScriptRoot $Project     # <profiles-root>\<Project>
$portFile   = Join-Path $profileDir 'DevToolsActivePort'

$chrome = @(
    "$env:ProgramFiles\Google\Chrome\Application\chrome.exe",
    "${env:ProgramFiles(x86)}\Google\Chrome\Application\chrome.exe",
    "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
) | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $chrome) { Write-Host 'Google Chrome not found.' -ForegroundColor Red; exit 1 }

# Chrome allows only one process per data directory. If a Chrome is already running on this
# folder, a new launch IGNORES the flags you pass -> check first so we do not open the wrong mode.
function Test-ProfileChromeRunning {
    $needle = "--user-data-dir=`"$profileDir`"".ToLowerInvariant()
    [bool](Get-CimInstance Win32_Process -Filter "Name='chrome.exe'" |
        Where-Object { $_.CommandLine -and $_.CommandLine.ToLowerInvariant().Contains($needle) })
}

# Return the port if the debug port in DevToolsActivePort answers, otherwise $null.
function Get-LiveDebugPort {
    try {
        if (-not (Test-Path $portFile)) { return $null }
        $port = (Get-Content $portFile -TotalCount 1).Trim()
        if (-not $port) { return $null }
        Invoke-RestMethod "http://127.0.0.1:$port/json/version" -TimeoutSec 2 | Out-Null
        return $port
    } catch { return $null }
}

if (Test-ProfileChromeRunning) {
    $port = Get-LiveDebugPort
    if ($Login) {
        if ($port) { Write-Host "Already open in work mode (port $port). Close it fully, then retry." -ForegroundColor Yellow; exit 1 }
        Write-Host 'Already open in login mode.' -ForegroundColor Green; exit 0
    }
    if ($port) { Write-Host "Ready for the MCP (port $port)." -ForegroundColor Green; exit 0 }
    Write-Host 'Open but no debug port (login mode). Close it fully, then retry.' -ForegroundColor Yellow
    exit 1
}

New-Item -ItemType Directory -Force $profileDir | Out-Null
$chromeArgs = @("--user-data-dir=`"$profileDir`"", '--no-first-run', '--no-default-browser-check')
if (-not $Login) {
    Remove-Item $portFile -Force -ErrorAction SilentlyContinue  # drop the previous run's stale port
    $chromeArgs += '--remote-debugging-port=0'
}
Start-Process -FilePath $chrome -ArgumentList $chromeArgs

if ($Login) {
    Write-Host 'Opened in login mode. Sign in, close it fully, then reopen work mode.' -ForegroundColor Green
    exit 0
}

for ($i = 0; $i -lt 40; $i++) {
    $port = Get-LiveDebugPort
    if ($port) { Write-Host "Opened for the MCP (port $port, local only)." -ForegroundColor Green; exit 0 }
    Start-Sleep -Milliseconds 250
}
Write-Host 'Opened but no debug port after 10 seconds.' -ForegroundColor Red
exit 1

The project's two double-click files

Put them in the project root, keep the contents ASCII (cmd reads batch files in the OEM code page). Substitute <project-slug> and the launcher path.

open-chrome-for-mcp.cmd (work mode):

@echo off
rem Opens this project's dedicated Chrome in work mode (random remote-debugging port)
rem so chrome-devtools-mcp (--autoConnect --userDataDir) can attach to it.
powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\chrome-mcp-profiles\open-chrome-mcp.ps1" -Project <project-slug>
if errorlevel 1 (pause) else (timeout /t 4 >nul)

open-chrome-for-mcp-login.cmd (login mode):

@echo off
rem Opens this project's dedicated Chrome in login mode (NO debug port) so Google / Cloudflare
rem sign-in behaves like a normal browser. Sign in, close that Chrome fully, then run
rem open-chrome-for-mcp.cmd. Cookies stay in the profile folder.
powershell -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\chrome-mcp-profiles\open-chrome-mcp.ps1" -Project <project-slug> -Login
if errorlevel 1 (pause) else (timeout /t 6 >nul)

-ExecutionPolicy Bypass applies only to that PowerShell process; it does not change any system setting.

The chrome-devtools entry in .mcp.json

JSON does not expand environment variables, so use an absolute path. In JSON, \ is \\.

{
  "mcpServers": {
    "chrome-devtools": {
      "command": "cmd",
      "args": [
        "/c", "npx", "-y", "[email protected]",
        "--autoConnect",
        "--userDataDir=<profiles-root>\\<project-slug>"
      ]
    }
  }
}

cmd /c is required on Windows because npx is a .cmd file. Pin the version (not @latest) to avoid pulling a just-published release - one layer of npm supply-chain defense. Do not add --isolated (it conflicts with both --autoConnect and --userDataDir), --browserUrl, or --executablePath.

Day-to-day use

WhenWhat to do
First time, or when a site asks you to sign in againClose the dedicated Chrome, run login mode, sign in, close it fully
You want the agent to work in the browserThe agent opens it when needed; or double-click the work-mode file
DoneClose the dedicated Chrome window - the login is kept
Wipe a project's dataClose the dedicated Chrome, delete <profiles-root>\<slug> (loses all logins)

While the agent works, you keep using your main Chrome normally. Pick a distinct theme color for this Chrome so it is easy to tell apart.

Security

  • While the dedicated Chrome is in work mode, any program on the machine can connect to the debug port (it listens on 127.0.0.1 only, not the network) and control that Chrome, including reading cookies. So: only sign in to the accounts a project truly needs, and close the dedicated Chrome when done.
  • Keep chrome://inspect/#remote-debugging OFF on your main Chrome - it is the one remaining way to open a debug port on the default profile.
  • The <profiles-root>\<slug> folder holds login cookies. Do not commit it, sync it to the cloud, or share it.
  • Consider extra flags: --redactNetworkHeaders (hide sensitive headers when reading network traffic), --allowedUrlPattern / --blockedUrlPattern (limit which pages the MCP may open).

Troubleshooting

SymptomFix
Could not connect to Chrome... ENOENT ... DevToolsActivePortThe dedicated Chrome is not open in work mode - run the launcher, then call the tool again
Could not connect... even though DevToolsActivePort existsA stale port file from the previous run (Chrome closed) or login mode is open - the launcher deletes the old file when it reopens
Google "This browser or app may not be secure"You are signing in with a debug port open / the MCP attached - use login mode (no port)
Non-ASCII text is garbled in the launcher windowopen-chrome-mcp.ps1 was saved without a BOM - resave it as UTF-8 with BOM
running scripts is disabled on this systemRunning the .ps1 directly under the default ExecutionPolicy - run it via the .cmd file
Launcher says "open but no debug port" even after you closed the windowChrome is still running in the background ("keep running background apps when closed") - quit it from the system tray, then retry
The MCP client does not show the chrome-devtools tool after editing .mcp.jsonThe session was not restarted, or the server is not approved - restart the MCP client

References

  • Chrome for Developers - Changes to remote debugging switches (Chrome 136): https://developer.chrome.com/blog/remote-debugging-port
  • chrome-devtools-mcp - Advanced usage (--autoConnect, --browser-url, debug-port security warning): https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/advanced-usage.md
  • Connect your AI agent to your personal browser with auto-connect (Chrome 144+): https://developer.chrome.com/docs/devtools/agents/use-cases/auto-connect
  • MDN - Navigator: webdriver property: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/webdriver
PreviousFix Claude Code connection errors on Windows (ECONNREFUSED, Bun crash)

Related articles

  • Token accounting for AI agents: tokens, context windows, and caching from first principles

    A model has no memory - everything that looks like memory is your code resending it. Get that one sentence right and you understand tokens, context windows, and prompt caching, and why an agent's bill balloons.

  • Clone a PostgreSQL database from a VPS to local Windows

    Make a full copy (schema and data) of a PostgreSQL database on a VPS onto a Windows dev machine using an SSH tunnel, pg_dump and pg_restore, including how to move the local PostgreSQL port out of the way.

    Database

    Database
  • Debug and bypass SSL pinning on an Android emulator

    Inspect the API traffic of an Android app that uses SSL pinning with LDPlayer 9 and HTTP Toolkit: enable root, connect over ADB, auto-bypass SSL, and handle advanced protections.

    Dev tools

    Dev tools

Written by Vu Van Hai

I'm Hai, a full-stack developer based in Ho Chi Minh City. These guides come from systems I built and run myself. Need to build or untangle something similar? Get in touch.

Discuss a projectMore guides

Spot a mistake or a command that no longer works? Let me know

On this page

  • The problem with the default setup
  • Comparing the launch methods
  • Why Chrome allows the debug port on a dedicated profile
  • Why sign-in is blocked, and how to avoid it legitimately
  • How it works
  • Setting up a project
  • The code
  • Launcher open-chrome-mcp.ps1
  • The project's two double-click files
  • The chrome-devtools entry in .mcp.json
  • Day-to-day use
  • Security
  • Troubleshooting
  • References