Playwright MCP error -32603: target page, context or browser has been closed
Playwright-32603 is the generic JSON-RPC "Internal error" code, and when the Playwright MCP server returns it with the message "target page, context or browser has been closed," it means a tool call like browser_navigate or browser_click reached into a Playwright object (page, context, or browser) that no longer exists on the server side. Unlike a plain Playwright script where you control the process lifetime directly, an MCP server keeps its own browser session alive between tool calls from your AI client, and that session can be torn down for reasons the client never sees.
Common mistake
{
"method": "tools/call",
"params": {
"name": "browser_navigate",
"arguments": { "url": "https://example.com/dashboard" }
}
}
Error -32603: Internal error target page, context or browser has been closed
The request looks fine, but the client is calling into a session that was already invalidated. Typical triggers:
- The MCP server process was restarted or crashed (OOM from a memory-heavy page, or the host killed it) and the client kept reusing a stale connection.
- The page navigated to something that crashed the tab, or the site itself called window.close().
- Two clients (or two chat sessions) are pointed at the same MCP server instance and one closed the browser or context the other was mid-use with.
- The server was started with --isolated, which creates a fresh in-memory browser context per connection, and the client reconnected or the server process cycled, wiping that context.
- A long idle gap between tool calls let a headless Chromium instance get reaped by the OS or container scheduler.
The fix
Start the server with an explicit, persistent user profile instead of relying on isolated, throwaway contexts, so a reconnect reuses state instead of starting from nothing:
npx @playwright/mcp@latest --user-data-dir=/path/to/profile
If you deliberately want a clean context per session (CI, untrusted automation), keep --isolated but add retry logic in the calling agent rather than assuming the session survives indefinitely:
async function callToolWithRetry(client, name, args, attempts = 2) {
for (let i = 0; i < attempts; i++) {
try {
return await client.callTool({ name, arguments: args });
} catch (err) {
const isClosed = /target page, context or browser has been closed/.test(err.message);
if (!isClosed || i === attempts - 1) throw err;
// Session died mid-flight — reconnect and let the server open a fresh browser
await client.close();
await client.connect();
}
}
}
For a one-off crashed page rather than a dead server, ask the model or client code to reissue browser_navigate to a fresh URL before touching the old page reference — most MCP hosts expose the current page implicitly, so a fresh navigation re-establishes it:
{ "name": "browser_navigate", "arguments": { "url": "https://example.com" } }
Why it works
A persistent profile (--user-data-dir) means the underlying Chromium process and its user data survive server restarts, so a reconnect after a crash reopens the same browser state instead of hitting a context that was garbage-collected. Retry-with-reconnect works because the MCP protocol treats the client-server link and the browser session as separate concerns: closing and reopening the client connection forces the server to spin up a new browser/context pair rather than trying to reuse a handle that's already dead. Re-navigating instead of reusing a stale page reference sidesteps the problem entirely, since browser_navigate always operates on whatever page the server currently considers active.
Tips
- Run npx @playwright/mcp@latest --help to confirm which flags your installed version supports — --isolated, --user-data-dir, and --browser behavior have changed across releases.
- If you're running the MCP server in a container or serverless function, watch memory limits closely; an OOM kill of the browser process is one of the most common silent causes of this error.
- Don't run multiple MCP clients against a single shared server instance unless you intend for them to share (and potentially disrupt) the same browser session — give each client its own server process or profile.
- This is distinct from the plain Playwright Test TargetClosedError — see how to fix target page, context or browser closed if you're hitting this in a .spec.ts test file rather than through MCP tool calls.