Skip to main content
Glama
chrome-devtools-mcp-profile-pin

chrome-devtools-mcp-profile-pin

by shraga100

chrome-devtools-mcp-profile-pin

Make chrome-devtools-mcp work with one Chrome profile of your choice when several Chrome profiles are open.

The wrapper patches your globally installed chrome-devtools-mcp in a separate copy, so the original install stays untouched. It re-applies itself automatically after every update of chrome-devtools-mcp.

Unofficial, not affiliated with Google. Upstream discussion: ChromeDevTools/chrome-devtools-mcp#694.

The problem

With --autoConnect, all open Chrome profiles share one browser process and one debugging endpoint. Chrome reports the last-focused profile as its default browser context, and that value changes as you switch windows. As a result:

  • the server binds to whichever profile happened to have focus when it connected;

  • list_pages mixes tabs from several profiles;

  • new_page opens in whichever profile has focus at that moment.

Related MCP server: chrome-mcp

What you get

  • The server sees exactly one profile. Pages, workers, service workers, console and network of other profiles are never attached.

  • It asks only when it has to. If only one profile has open windows, nothing changes. If several are open, you are asked which one to use, with one choice per profile, and the answer is remembered. The question comes either as an MCP elicitation form or, where the client can't show forms, from the agent itself (see below).

  • You can see which profile is attached. list_pages shows it in the header, e.g. ## Pages (Chrome profile: Work (Profile 1)).

  • A select_profile tool. Ask the agent to "switch to my work profile". With no arguments, the tool lists your profiles.

  • New tabs always open in the attached profile. If that profile is closed, its window is opened for you.

How the profile is chosen

The first matching rule wins:

  1. select_profile was called in this session.

  2. The CHROME_DEVTOOLS_MCP_PROFILE environment variable is set to a display name or folder name. Set it to off to disable pinning completely.

  3. Only one profile has open windows: no pinning, same as upstream.

  4. You chose a profile earlier and asked to remember it.

  5. The client shows an MCP elicitation form: a single-select list of your profiles.

  6. Otherwise the tool call returns a message that lists each profile with the exact select_profile call for it. It tells the agent to ask you with one choice per profile. In Claude Code, for example, the agent asks with one button per profile, and the call is retried after you pick one.

Some clients advertise elicitation but have no UI for it and decline automatically. The Claude desktop app's Code tab runs Claude Code in SDK/print mode and does this. In that case the wrapper falls back to step 6.

Requirements

  • Node.js 22.12+. The wrapper uses Node's built-in WebSocket.

  • Google Chrome 144+, with remote debugging enabled at chrome://inspect/#remote-debugging (the normal --autoConnect setup).

  • chrome-devtools-mcp installed globally:

    npm install -g chrome-devtools-mcp@latest

Tested with chrome-devtools-mcp 1.12.0 and Chrome 157 on Windows. The macOS and Linux code paths exist but are untested.

Installation

  1. Clone the repository somewhere permanent:

    git clone https://github.com/shraga100/chrome-devtools-mcp-profile-pin.git
  2. Point your MCP client at launch.mjs instead of chrome-devtools-mcp, and keep your usual flags.

    Claude Code:

    claude mcp add chrome-devtools --scope user -- node /path/to/chrome-devtools-mcp-profile-pin/launch.mjs --autoConnect

    Any client using JSON config (~/.claude.json, .mcp.json, Cursor, etc.):

    {
      "mcpServers": {
        "chrome-devtools": {
          "command": "node",
          "args": ["/path/to/chrome-devtools-mcp-profile-pin/launch.mjs", "--autoConnect"]
        }
      }
    }

    To always use one profile, add "env": { "CHROME_DEVTOOLS_MCP_PROFILE": "Work" }.

  3. Start a new session. On first start the wrapper builds the patched copy into dist/, which takes a few seconds.

Sessions that are already running keep using whatever they started with. Only new sessions use the wrapper.

Staying patched after chrome-devtools-mcp updates

You don't need to do anything special. Update chrome-devtools-mcp as usual:

npm install -g chrome-devtools-mcp@latest

On the next start, launch.mjs notices the new version, builds a fresh patched copy in dist/<version>-<hash>/ and runs it. Old copies are kept for 14 days, because long-running sessions may still be using them, and are then deleted.

If a new upstream version changed the code the patch relies on, the wrapper does not break your MCP. It logs a warning to stderr and runs the unpatched upstream server, so you only lose profile pinning. Check for an update of this repository:

git pull

To check the status manually:

node build.mjs           # build if needed and print status
node build.mjs --force   # rebuild

Configuration

Environment variable

Meaning

CHROME_DEVTOOLS_MCP_PROFILE

Profile to use: a display name (Work) or folder name (Profile 1). auto (default) or off.

CHROME_DEVTOOLS_MCP_CHROME_PATH

Chrome executable, if it isn't found automatically.

CHROME_DEVTOOLS_MCP_SOURCE

Path of the chrome-devtools-mcp package to patch, if it isn't the global npm one.

CHROME_DEVTOOLS_MCP_PROFILE_STATE_DIR

Where the remembered choice is stored. Default: ~/.chrome-devtools-mcp-profile-pin.

CHROME_DEVTOOLS_MCP_PROFILE_DEBUG

Set to 1 to log connection timings to stderr.

The select_profile tool takes these arguments:

  • profile: the profile to switch to.

  • remember: default true.

  • forget: clears the remembered choice.

Switching reconnects to Chrome, so Chrome may show "Allow remote debugging?" again.

How it works

  • The wrapper opens one WebSocket to Chrome, so the consent dialog appears once. It resolves the profile on that socket and then hands the same socket to Puppeteer through puppeteer.connect({transport}).

  • To find a profile's browserContextId, it runs chrome --profile-directory=<dir> "data:text/html,<!--nonce-->". The running Chrome opens that tab inside the profile, opening the profile's window if needed, and the wrapper looks for the nonce in Target.targetCreated. It runs no script in your pages. If the profile already has other tabs, the helper tab is closed again. A running Chrome ignores about: and chrome:// URLs from the command line, which is why the helper tab uses a data: URL.

  • Puppeteer's targetFilter rejects targets whose browserContextId belongs to another profile. Puppeteer doesn't run that filter for service workers, so the wrapper also filters browser.targets() and the targetcreated/targetchanged/targetdestroyed events. Without that, extensions installed in several profiles would show up once per profile under "Extension Service Workers".

  • new_page first tries Target.createTarget({browserContextId}). Chrome only accepts that while the profile is the last-focused one, so on failure the wrapper falls back to the command-line trick.

  • The patch is applied to the built JavaScript with exact-match anchors (lib/patch.mjs). If any anchor is missing, the build fails as a whole rather than half-applying.

Limitations

  • Chrome still asks "Allow remote debugging?" on every new connection. This wrapper doesn't change that.

  • When a pinned session connects, the profile's window comes to the front, and a helper tab may stay open if the profile had no other tabs.

  • When new_page uses the fallback, background: true can't be honoured.

  • If every window of the pinned profile is closed while connected, the next new_page reconnects. Chrome gives the reopened profile a new id.

  • Only local Chrome connections are supported (--autoConnect, or --browserUrl pointing at localhost).

Testing

test/e2e.mjs drives the server over stdio against your real Chrome. It uses a temporary state directory, so your remembered choice is not touched.

node test/e2e.mjs offline
node test/e2e.mjs fixed "Work"
node test/e2e.mjs elicit "Profile 1" "Default"   # 2+ profiles must be open
node test/e2e.mjs no-elicit                      # 2+ profiles must be open

License

Apache-2.0, the same as chrome-devtools-mcp.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Wraps chrome-devtools-mcp to control Brave/Chrome/Edge browsers with real user profiles, supporting multi-browser management, hot switching, and automatic reconnection.
    34
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Local MCP server for persistent Chrome automation with multi-profile support, enabling tab management, page inspection, element interaction, JavaScript evaluation, and screenshots while preserving login sessions across restarts.
    112 npm
    3
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables MCP clients to drive individually named Chrome profiles, providing tools for tabs, navigation, page interaction, screenshots, JavaScript evaluation, console logs, and network inspection over stdio without a TCP port.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Connects AI agents to an existing Chromium browser profile through a local background service and extension, exposing browser control over MCP stdio. It lets agents navigate tabs, capture accessibility snapshots and screenshots, click, fill forms, type, scroll, drag, and run raw CDP commands, with multiple browser profiles selectable by ID.
    5 npm
    Do What The F*ck You Want To Public