From bafc5035caddedd45e0c77eec6013eda94b6e870 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Fri, 22 May 2026 05:12:31 -0700 Subject: [PATCH 1/2] fix(mcp): use writable cache dir for MCP user data, not browsers path createUserDataDir() built the MCP profile path from registryDirectory, which is rooted at PLAYWRIGHT_BROWSERS_PATH when that env var is set. That path is often read-only (NixOS Nix store, immutable Docker mounts, shared browser-binary caches), so MCP fails to launch with EACCES while trying to mkdir the profile directory. Use defaultCacheDirectory (XDG_CACHE_HOME on Linux, Library/Caches on macOS, LOCALAPPDATA on Windows) and namespace the profiles under ms-playwright-mcp. The PWMCP_PROFILES_DIR_FOR_TEST override is unchanged. Fixes: https://github.com/microsoft/playwright/issues/40892 --- packages/playwright-core/src/tools/mcp/browserFactory.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/tools/mcp/browserFactory.ts b/packages/playwright-core/src/tools/mcp/browserFactory.ts index 2f33851a7c566..6f720335eb3f4 100644 --- a/packages/playwright-core/src/tools/mcp/browserFactory.ts +++ b/packages/playwright-core/src/tools/mcp/browserFactory.ts @@ -19,7 +19,7 @@ import fs from 'fs'; import path from 'path'; import { playwright } from '../../inprocess'; -import { registryDirectory } from '../../server/registry/index'; +import { defaultCacheDirectory } from '../../server/registry/index'; import { testDebug } from './log'; import { outputDir } from '../backend/context'; import { createExtensionBrowser } from './extensionContextFactory'; @@ -182,7 +182,10 @@ async function createPersistentBrowser(config: FullConfig, clientInfo: ClientInf } async function createUserDataDir(config: FullConfig, clientInfo: ClientInfo) { - const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? registryDirectory; + // Profile data must be writable, so we never derive it from PLAYWRIGHT_BROWSERS_PATH + // (which often points at a read-only browser binary cache on NixOS, immutable Docker + // mounts, or shared filesystems). Use the platform's user cache directory instead. + const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? path.join(defaultCacheDirectory, 'ms-playwright-mcp'); const browserToken = config.browser.launchOptions?.channel ?? config.browser?.browserName; // Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead. const rootPathToken = createHash(clientInfo.cwd); From d5ef3b29cfbe5bb7bcd30c521953d2dd4d763e56 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 26 May 2026 11:57:32 -0700 Subject: [PATCH 2/2] Remove comments about user data directory handling Removed comments explaining the writable profile data requirement. Signed-off-by: Pavel Feldman --- packages/playwright-core/src/tools/mcp/browserFactory.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/playwright-core/src/tools/mcp/browserFactory.ts b/packages/playwright-core/src/tools/mcp/browserFactory.ts index 6f720335eb3f4..ab171f1ebe6f1 100644 --- a/packages/playwright-core/src/tools/mcp/browserFactory.ts +++ b/packages/playwright-core/src/tools/mcp/browserFactory.ts @@ -182,9 +182,6 @@ async function createPersistentBrowser(config: FullConfig, clientInfo: ClientInf } async function createUserDataDir(config: FullConfig, clientInfo: ClientInfo) { - // Profile data must be writable, so we never derive it from PLAYWRIGHT_BROWSERS_PATH - // (which often points at a read-only browser binary cache on NixOS, immutable Docker - // mounts, or shared filesystems). Use the platform's user cache directory instead. const dir = process.env.PWMCP_PROFILES_DIR_FOR_TEST ?? path.join(defaultCacheDirectory, 'ms-playwright-mcp'); const browserToken = config.browser.launchOptions?.channel ?? config.browser?.browserName; // Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead.