Jahanzaib

How to Set Up the Playwright MCP Server Without Handing It Your Logins

Install Microsoft's Playwright MCP server in Claude Code with an isolated profile, test logins from a storage state file, secrets kept out of the chat, and measured snapshot token costs.

Jahanzaib Ahmed
13 min read
Claude and Playwright logos on dark tiles joined by an arrow, for a Playwright MCP server setup guide

The Playwright MCP server lets Claude Code open a real browser, click through your app and read what's on the page, so it can check its own work instead of guessing. The server is free; you pay only for the tokens its page snapshots use. This guide is for developers who want browser access for their coding agent without handing it a browser that remembers every login.

The install is one line. Left at its defaults, that line gives Claude Code a browser profile that keeps cookies on disk between sessions and writes page text into your project folder, and nothing stops you from pasting a password into the chat for it to type. The rest of this guide closes those gaps. I ran version 0.0.83 of Microsoft's Playwright MCP server through a bare MCP client on October 6, 2026 and measured what it sends back to the model. Those numbers are in Step 8.

Before you start

  • Node.js 18 or newer. The README lists it as the only hard requirement. Check with node --version.
  • Claude Code, installed and signed in. Any MCP client works (Cursor, VS Code, Claude Desktop), but the commands below are for Claude Code.
  • Google Chrome installed. The server launches the Chrome channel by default.
  • A test account for any site you want the agent to log into. Never your personal one.
  • Cost: the server is open source under the Apache 2.0 license. What you pay for is model tokens, and page snapshots can be large.

If you're new to the protocol, my guide to building MCP servers for production explains how an MCP server exposes tools to a client like Claude Code. You don't need it to follow along.

Step 1: Pin a version instead of using @latest

Every official snippet uses @playwright/mcp@latest. That means npx can pull a new release the next time your client restarts, and this project ships often: version 0.0.83 landed on September 28, 2026, ten days after 0.0.82. Recent releases changed default behavior, such as v0.0.78 shortening snapshots and v0.0.82 turning tools a page registers into new MCP tools. Pinning means a change reaches you when you choose it.

node --version
npm view @playwright/mcp version

The second command prints the current release. I use 0.0.83 in every command below because it was current when I wrote this; swap in whatever number you get, and bump it on purpose after reading the release notes.

Step 2: Add the Playwright MCP server to Claude Code with an isolated profile

The README's Claude Code command is claude mcp add playwright npx @playwright/mcp@latest. I add two things. The Claude Code MCP docs say to put the whole launch command after --, so flags like -y go to npx rather than to Claude Code. And I add --isolated.

claude mcp add playwright -- npx -y @playwright/mcp@0.0.83 --isolated

Without --isolated, the server uses a persistent profile stored at ~/Library/Caches/ms-playwright/mcp-{channel}-{workspace-hash} on macOS (~/.cache/ms-playwright/ on Linux). The README is plain about it: "All the logged in information will be stored in the persistent profile." Log the agent into a staging admin panel once, and every future session in that project starts already logged in. In isolated mode the profile lives in memory and is gone when the browser closes. Every flag I use in this guide is listed in the official options reference.

Comparison of Playwright MCP profile modes: persistent profile keeps logins on disk, isolated session keeps nothing, browser extension uses your real tabs
The three ways the server can run a browser, and what each one remembers after the session ends.

Pick the scope that fits. Claude Code stores the server in one of three places:

ScopeFlagStored inUse it when
Local (default)none~/.claude.json, this project onlyYou're trying it out
Project--scope project.mcp.json in the repoYour team should get the same setup
User--scope user~/.claude.json, all projectsYou want it everywhere

For a team, the project file is the one to commit, because the flags in it are the policy. I show a full .mcp.json in Step 7.

Step 3: Confirm it connected and run a first task

Run claude mcp list. Next to playwright you should see the status Connected. If it says Failed to connect, jump to Troubleshooting.

Start claude in your project and ask for something small with a checkable answer:

Use the Playwright MCP server to open https://example.com and tell me the page heading.

A Chrome window opens (the server runs headed unless you pass --headless), Claude asks permission to use browser_navigate, and it answers "Example Domain". Name the server in that first prompt. Simon Willison found that without it, Claude sometimes tried to run Playwright through Bash instead.

Diagram of Claude Code calling the Playwright MCP server, which loads a secrets file and storage state, drives an isolated browser and writes snapshot files
Claude Code never touches the browser directly: every action goes through the server, which also decides where state comes from and where files land.

Step 4: Keep snapshot files out of git

I expected the snapshot to come back inline. It didn't. browser_navigate wrote it to .playwright-mcp/page-<timestamp>.yml in the working directory and returned a 221 to 306 character pointer to it. In my login test, the console log went into the same folder. Three page loads left 122 KB of YAML in my folder, and those files hold whatever text was on screen, including data from logged in pages.

echo ".playwright-mcp/" >> .gitignore

If you'd rather keep them outside the repo entirely, the server takes --output-dir <path> for automatically named files. Files Claude saves with an explicit name still resolve against the workspace root, so the gitignore line is worth having either way.

Step 5: Give the agent a test login with a storage state file

Isolated mode forgets everything, which is what you want by default but awkward when the page you're testing sits behind a login. The fix is to log in once yourself, save the cookies and local storage to a file, and load that file into each fresh session. Playwright's codegen tool does the saving:

mkdir -p playwright/.auth
echo "playwright/.auth" >> .gitignore
npx playwright codegen http://localhost:3000/login --save-storage=playwright/.auth/user.json

A browser opens. (If codegen says no browser is installed, run npx playwright install chromium first.) Log in with your test account, then close the window. The file now holds that session. Playwright's authentication docs warn that this file "may contain sensitive cookies and headers" that could be used to impersonate the account, which is why it goes in .gitignore before it exists.

Now point the server at it. Remove it and add it again with the extra flag:

claude mcp remove playwright
claude mcp add playwright -- npx -y @playwright/mcp@0.0.83 --isolated --storage-state=playwright/.auth/user.json

Ask Claude to open a page that needs the login. It should land inside the app, not on the sign in form. When the session expires, rerun codegen. I'd rather refresh a file every few days than leave a long lived login sitting in a profile folder I never look at.

Step 6: Pass passwords through a secrets file, not the chat

Sometimes the agent has to type a credential, for example when you're testing the login form itself. The server has a --secrets option that reads a dotenv file. The model never sees the value: it passes the secret's name, and the server swaps in the real value. I tested it against a public demo login page, using a file with one entry named DEMO_PASSWORD. For your own project, create the file and add the flag:

# .env.playwright
TEST_PASSWORD=your-test-account-password
echo ".env.playwright" >> .gitignore
claude mcp remove playwright
claude mcp add playwright -- npx -y @playwright/mcp@0.0.83 --isolated --storage-state=playwright/.auth/user.json --secrets=.env.playwright

Then ask for it by name: "type TEST_PASSWORD into the password field". Claude sends the literal string TEST_PASSWORD, and the server does the rest. In my test the entry was called DEMO_PASSWORD.

When the client called browser_type with the text DEMO_PASSWORD, the server reported the action as fill(process.env['DEMO_PASSWORD']) and filled the real password. When I then read the field's value back with browser_evaluate, the response said <secret>DEMO_PASSWORD</secret> instead of the password.

Warning: the redaction is a plain text find and replace on tool responses. The config docs call secrets "a convenience and not a security feature". A page that shows the value encoded or split up would get past it. Use it for test accounts only.

Step 7: Remove the tools you don't need and commit the config

My test server exposed 25 tools. Two of them run arbitrary code in the page: browser_evaluate and browser_run_code_unsafe. Most QA tasks need neither. Claude Code's permission rules name MCP tools as mcp__<server>__<tool>, and a deny rule on a bare tool name takes the tool out of Claude's context. In .claude/settings.json:

{
  "permissions": {
    "deny": [
      "mcp__playwright__browser_run_code_unsafe",
      "mcp__playwright__browser_evaluate"
    ]
  }
}

Then put the whole server definition in a project scoped .mcp.json so everyone gets the same flags. First run claude mcp remove playwright: Claude Code uses only one definition per server name, and a local scope entry from Steps 2 to 6 outranks the project file, so you'd keep running your old flags without noticing.

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "-y",
        "@playwright/mcp@0.0.83",
        "--isolated",
        "--storage-state=playwright/.auth/user.json",
        "--secrets=.env.playwright"
      ]
    }
  }
}

The next time you start Claude Code, claude mcp list shows the server as pending approval until you approve it in an interactive session. Those paths are relative, so start claude from the project root. If you launch it from a subfolder, swap in absolute paths.

You'll see --allowed-origins recommended for this job. Use it to keep the agent focused on your app if you like, but the README says it "does not serve as a security boundary and does not affect redirects", and it states flatly that Playwright MCP is not a security boundary. Every page the agent reads is untrusted input, and prompt injection through page text is the obvious way in. The MCP security best practices cover the wider threat model. What actually limits the damage is what the browser can reach, so give it a test account in an isolated profile and apply least privilege to everything else.

Step 8: Check how big Playwright MCP snapshots get

I connected to the server with a minimal MCP client, headless and isolated, and measured the responses:

WhatCharactersRough tokens (chars ÷ 4)
Tool definitions (25 tools)20,286about 5,000
browser_snapshot of example.com1,272about 300
browser_snapshot of the Playwright intro docs page32,549about 8,100
browser_snapshot of the Wikipedia MCP article89,034about 22,000

The four characters per token rule is an estimate, not a tokenizer count, but the spread is the point: a content heavy page costs about 70 times what a simple one does. Claude Code softens two of these. Tool search is on by default, so MCP tool definitions are deferred and loaded on demand rather than sitting in every request. And text results over 50,000 characters are saved to a file instead of going straight into the context window, which is exactly what happens to that Wikipedia snapshot.

What I'd do on top:

  1. Ask for browser_find on big pages. Added in v0.0.78, it searches the snapshot for text and returns only the matching nodes with a little context.
  2. Try --mobile for content sites. The README notes mobile pages are usually lighter, which saves tokens.
  3. Give specific instructions. "Check the checkout total on /cart" leads to one snapshot. "Look around the app" leads to dozens.

On a long agent session these numbers add up the same way any agent loop's token bill does: every turn resends what came before.

Debbie O'Brien explains the difference between Playwright's browser automation MCP server and its test MCP server, and when each one fits. Useful if you're deciding which one you need.

Troubleshooting

Claude runs Playwright through Bash instead of the MCP tools

Say "use the Playwright MCP server" in the prompt, as in Step 3. If it keeps happening, check /mcp inside Claude Code to confirm the server is connected for this project, since local scope only applies to the folder where you added it.

"Browser is already in use for ..., use --isolated to run multiple instances"

A persistent profile can only be used by one browser at a time, so two Claude Code sessions in the same project collide. The error in issue 769 names the fix: run with --isolated, or give each client its own --user-data-dir.

"Browser specified in your config is not installed"

The default channel is Chrome, which Playwright doesn't install for you. Install Google Chrome, run npx playwright install chrome (the browsers docs cover branded browser installs), or pass --browser firefox or --browser msedge for a browser you already have.

The server fails to connect the first time

The first npx run downloads the package, which can take longer than the startup timeout. Start Claude Code with a longer one: MCP_TIMEOUT=60000 claude.

No browser window appears, or it fails on a server

Headed mode needs a display. On a remote machine or in CI, add --headless. If your client can't start it, run it yourself with npx @playwright/mcp@0.0.83 --port 8931 and point the client at http://localhost:8931/mcp.

Claude Code warns that a tool result is too large

Claude Code warns above 10,000 tokens and caps MCP output at 25,000 by default. Use browser_find as in Step 8 before reaching for MAX_MCP_OUTPUT_TOKENS, because raising the cap just makes each turn more expensive.

Going further: MCP, the CLI, or Docker

Microsoft now ships a second way to do this. The Playwright CLI with skills lets a coding agent drive the browser through short commands, and the MCP README itself says coding agents may be better served by it because it avoids loading large tool schemas and accessibility trees into context. My rule:

OptionPick it whenWatch out for
Playwright MCPThe agent explores a page and decides what to do next, or you use an MCP client without shell accessLarge snapshots on content heavy pages
Playwright CLI and skillsA coding agent in a big repo needs browser checks alongside lots of code readingYour agent needs to be able to run shell commands
Docker imageYou want the browser off your machine entirelyHeadless Chromium only, per the README

When not to use any of them: if you already know the exact clicks, write a normal Playwright test. An agent exploring the page each run costs tokens every time; a test costs them once while you write it. I use the agent to find the flow and a test to keep it working. If you're debugging the agent itself rather than the app, LangGraph Studio is the better tool, and for the bigger picture of agents driving computers, see where computer use stands in 2026.

Exposing other systems to Claude follows the same pattern of scoping first. My guide to connecting Claude to n8n's MCP server applies the same thinking to workflows. If you want a browsing agent built and monitored for you instead, see how I build agents.

Frequently asked questions

Is the Playwright MCP server free?

Yes. Microsoft publishes it as open source under the Apache 2.0 license, and it runs locally through npx with no account or API key. Your real cost is model usage: every page snapshot the server returns counts as input tokens in your Claude Code session, and a content heavy page can run past 20,000 tokens.

Does Playwright MCP need a vision model or screenshots?

No. It reads the page's accessibility tree, the structured list of headings, links, buttons and fields that screen readers use, and sends that as text. The model clicks elements by reference from that tree. Screenshots and coordinate based clicking exist as options, but the coordinate tools are off unless you enable them with --caps=vision.

What's the difference between @playwright/mcp and other Playwright MCP packages?

The official server is @playwright/mcp, maintained by Microsoft in the microsoft/playwright-mcp repository. Community packages with similar names exist, such as @executeautomation/playwright-mcp-server, with different tools and options. If a tutorial's flags don't match what you see, check which package it installed. Everything in this guide applies to the official one.

Can Playwright MCP use my logged in Chrome?

Yes, through the Playwright browser extension and the --extension flag, which connects to tabs in a running Chrome or Edge. I avoid it for agent work because the agent then acts with every session that browser holds, your email included. An isolated profile plus a storage state file for a test account gives the agent only what the task needs.

How do I run Playwright MCP headless?

Add --headless to the server's arguments, for example claude mcp add playwright -- npx -y @playwright/mcp@0.0.83 --isolated --headless. The server runs headed by default. Headless browsers also close after an hour without a completed tool call by default, and the next call relaunches them, which you can change with --idle-timeout.

Feed to Claude or ChatGPT

Published

October 6, 2026

Category

AI Agents
Jahanzaib Ahmed

Jahanzaib Ahmed

AI Systems Engineer & Founder

AI Systems Engineer with 126 production systems shipped. I run AgenticMode AI (AI agents, RAG systems, voice AI) and ECOM PANDA (ecommerce agency). I build AI that works in the real world for businesses across home services, healthcare, ecommerce, SaaS, and real estate.