From bafc5035caddedd45e0c77eec6013eda94b6e870 Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Fri, 22 May 2026 05:12:31 -0700 Subject: [PATCH] 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);