feat: add storage-isolated browser contexts#991
Conversation
OrKoN
left a comment
There was a problem hiding this comment.
Thanks for the PR but I think we can implement it without new tools, see #926 (comment)
af249c2 to
2763aa0
Compare
|
Thanks for the feedback, @OrKoN — you're right, the simpler design is much better. I've reworked the PR to follow your suggestion from #926 exactly:
Additional details:
All 333 existing tests pass, zero type errors. I've also updated the PR description to reflect the new design. Ready for another look when you have time. |
29c0800 to
cb0f182
Compare
OrKoN
left a comment
There was a problem hiding this comment.
Thanks, I think the PR needs some polishing (left comments)
cb0f182 to
f84a246
Compare
|
Thanks for the thorough review @OrKoN! All 6 items addressed in the new force-pushed commit ( Here's what changed:
The commit is squashed into a single clean commit on top of |
f84a246 to
cd9c325
Compare
|
Removed all The previous tests were for the
|
cd9c325 to
070c5eb
Compare
OrKoN
left a comment
There was a problem hiding this comment.
Thanks! I think this mostly looks good, I will double check on Monday and I think we would be able to merge it then. cc @natorion @matthiasrohmer
070c5eb to
7a049be
Compare
|
Done. Removed the entire |
|
Hey @RobertWsp and @OrKoN , thanks for this PR - the Ran into a race condition with the shared The problem: Reproduced it reliably: two isolated contexts, different URLs, two agents selecting+screenshotting in parallel. The agent that selects first consistently captures the wrong page. Suggested fix: add an optional The infrastructure is already there ( Should I open a separate issue to track this or here would be the right place? |
|
@passtas we specifically decided against a design where each tool needs a page id because usually multiple operations are performed on one page. Having pageId is each tool call increases the tokens consumed for each LLM iteration. Do you have evals that prove that this works better (in terms of token usage and ability of LLMs to use the tools)? cc @natorion |
|
@OrKoN, that is of course fair point. I'd expect some marginal increase in token usage as the agent will have to remember and pass through the I guess the trick would also be to have it backward compatible by making this attribute optional, so that use will have to opt-in by putting specific prompt about context isolation etc., if he wouldn't he would still have current behaviour. resolvePage(pageId?: number): Page {
if (pageId !== undefined) {
return this.getPageById(pageId);
}
return this.getSelectedPage();
}And the description: export const pageIdSchema = {
pageId: zod
.number()
.optional()
.describe(
'The ID of the page to operate on. If omitted, uses the currently selected page. Use this to avoid race conditions when multiple agents work in parallel.',
),
};My subjective thinking is that without ability to do meaningful work (all mentioned tools use) within browser isolated context - it doesn't make sense to release this PR, as it will lead to the unexpected behaviour. As a user of agents and teams of agents, I'd like those subagents to be able to work accurately in within their sessions. Browser context isolation provides a bit of that capability, but doesn't enable those separate sessions to work to the 100% of the mcp capability. I did the fix locally to see if it will work, and it does - opens separate isolated windows and does operations with it locally without loosing that Let me think on the potential eval to prove myself right or wrong about the token usage hypothesis. I'd also like to explore if the Thanks. |
@passtas I am not sure I follow, the select_page can be used to switch to a page in the isolated context. |
this is not race-prone because we have an internal mutex that processes all commands one by one. |
…port Add optional `isolatedContext` parameter to `new_page` tool that creates pages in isolated browser contexts (separate cookies, storage, WebSocket connections). This enables testing multi-user scenarios where an LLM needs simultaneous sessions as different users. Implementation: - new_page accepts optional isolatedContext string parameter - McpContext manages a Map of named BrowserContexts - Pages created with the same context name share an isolated environment - Pages list displays context labels for easy identification - Uses page.browserContext() for reverse-lookup instead of iterating contexts Closes ChromeDevTools#926
9640004 to
c03a05a
Compare
that's precisely what creates the problem. Here's the sequence:
Each call is atomic, but between Agent A's The mutex guarantees no two tool calls execute simultaneously, but it doesn't guarantee that a two-call sequence from one agent runs without another agent's calls interleaving. Agent A has no way to hold the mutex across both calls. |
|
@passtas this is not correct usage of the tools, before any tool call is made the right page needs to be selected. |
|
Also, multi-agent flow is not very common, each client should have its own MCP server instance in the current design. Can you describe your set up in more details? perhaps it's best to continue the discussion on the feature issue. |
|
Created #1019, so let's continue there. |
🤖 I have created a release *beep* *boop* --- ## [0.18.0](chrome-devtools-mcp-v0.17.3...chrome-devtools-mcp-v0.18.0) (2026-02-24) ### 🎉 Features * `--slim` mode for maximum token savings ([#958](#958)) ([c402b43](c402b43)) * add a new skill for accessibility debugging and auditing with Chrome DevTools MCP. ([#1002](#1002)) ([b0c6d04](b0c6d04)) * add experimental screencast recording tools ([#941](#941)) ([33446d4](33446d4)) * add skill to debug and optimize LCP ([#993](#993)) ([2cd9b95](2cd9b95)) * add storage-isolated browser contexts ([#991](#991)) ([59f6477](59f6477)) * add take_memory_snapshot tool ([#1023](#1023)) ([7ffdc5e](7ffdc5e)) * support any-match text arrays in wait_for ([#1011](#1011)) ([496ab1b](496ab1b)) * support type_text ([#1026](#1026)) ([b5d01b5](b5d01b5)) ### 🛠️ Fixes * detect X server display on Linux ([#1027](#1027)) ([1746ed9](1746ed9)) ### ♻️ Chores * cleanup string and structured console formatters ([#1005](#1005)) ([0d78685](0d78685)) * extract version in a seprate file ([#1032](#1032)) ([0106865](0106865)) * move emulation settings to context ([#1000](#1000)) ([bc3c40e](bc3c40e)) * optimize slim tool descriptions and params ([#1028](#1028)) ([ca6635d](ca6635d)) * simplify JavaScript code examples, update code block language, and refine descriptions in a11y debugging skill documentation. ([#1009](#1009)) ([5cedcaa](5cedcaa)) * types for JSON output of IssueFormatter ([#1007](#1007)) ([9ef4479](9ef4479)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
## Summary Adds storage-isolated browser contexts via an optional `isolatedContext` parameter on the `new_page` tool, following the simplified design proposed by @OrKoN in ChromeDevTools#926. Pages created with the same `isolatedContext` name share cookies, localStorage, and storage. Pages in different isolated contexts (or the default context) are fully isolated — ideal for testing multi-user real-time features like chat, notifications, or collaborative editing. ## Changes ### `new_page` tool - New optional `isolatedContext: string` parameter - If specified, creates/reuses a named `BrowserContext` and opens a page in it - If omitted, uses the default browser context (existing behavior unchanged) ### `McpContext` - `#isolatedContexts` Map: LLM-provided names → Puppeteer `BrowserContext` instances - `#pageToIsolatedContextName` WeakMap: GC-safe page → context name reverse lookup - Auto-discovery: externally created browser contexts get `isolated-context-1`, `isolated-context-2`, etc. - `getIsolatedContextName(page)`: returns the isolated context name for a page (used by response formatting) - `page.browserContext()` used for context membership detection (no custom target event forwarding needed) - No context cleanup in `dispose()` or `closePage()` — either the entire browser is closed or we disconnect without destroying state ### `McpResponse` - Page list includes `isolatedContext=${name}` labels (both text and structured JSON output) ### `ToolDefinition` - `Context` interface extended with `getIsolatedContextName(page)` method ## What's NOT included (by design) - **No `TargetEventEmitter`**: Puppeteer forwards target events from `BrowserContext` → `Browser` internally - **No context cleanup**: Browser contexts are not closed on `dispose()` or page close, per maintainer guidance - **No `about:blank` cleanup**: Default context and isolated contexts coexist side-by-side ## Example ``` > new_page url="https://app.example.com/chat" isolatedContext="userA" > new_page url="https://app.example.com/chat" isolatedContext="userB" > list_pages Page 1: [app.example.com/chat] isolatedContext=userA Page 2: [app.example.com/chat] isolatedContext=userB [selected] ``` Pages in different isolated contexts have fully independent cookies, localStorage, IndexedDB, and WebSocket connections. ## Tests - 6 new tests covering `isolatedContext` feature in `tests/tools/pages.test.ts` - All existing tests pass (333+) - Zero type errors, lint clean Closes ChromeDevTools#926 --------- Co-authored-by: Alex Rudenko <alexrudenko@chromium.org>
🤖 I have created a release *beep* *boop* --- ## [0.18.0](ChromeDevTools/chrome-devtools-mcp@chrome-devtools-mcp-v0.17.3...chrome-devtools-mcp-v0.18.0) (2026-02-24) ### 🎉 Features * `--slim` mode for maximum token savings ([ChromeDevTools#958](ChromeDevTools#958)) ([c402b43](ChromeDevTools@c402b43)) * add a new skill for accessibility debugging and auditing with Chrome DevTools MCP. ([ChromeDevTools#1002](ChromeDevTools#1002)) ([b0c6d04](ChromeDevTools@b0c6d04)) * add experimental screencast recording tools ([ChromeDevTools#941](ChromeDevTools#941)) ([33446d4](ChromeDevTools@33446d4)) * add skill to debug and optimize LCP ([ChromeDevTools#993](ChromeDevTools#993)) ([2cd9b95](ChromeDevTools@2cd9b95)) * add storage-isolated browser contexts ([ChromeDevTools#991](ChromeDevTools#991)) ([59f6477](ChromeDevTools@59f6477)) * add take_memory_snapshot tool ([ChromeDevTools#1023](ChromeDevTools#1023)) ([7ffdc5e](ChromeDevTools@7ffdc5e)) * support any-match text arrays in wait_for ([ChromeDevTools#1011](ChromeDevTools#1011)) ([496ab1b](ChromeDevTools@496ab1b)) * support type_text ([ChromeDevTools#1026](ChromeDevTools#1026)) ([b5d01b5](ChromeDevTools@b5d01b5)) ### 🛠️ Fixes * detect X server display on Linux ([ChromeDevTools#1027](ChromeDevTools#1027)) ([1746ed9](ChromeDevTools@1746ed9)) ### ♻️ Chores * cleanup string and structured console formatters ([ChromeDevTools#1005](ChromeDevTools#1005)) ([0d78685](ChromeDevTools@0d78685)) * extract version in a seprate file ([ChromeDevTools#1032](ChromeDevTools#1032)) ([0106865](ChromeDevTools@0106865)) * move emulation settings to context ([ChromeDevTools#1000](ChromeDevTools#1000)) ([bc3c40e](ChromeDevTools@bc3c40e)) * optimize slim tool descriptions and params ([ChromeDevTools#1028](ChromeDevTools#1028)) ([ca6635d](ChromeDevTools@ca6635d)) * simplify JavaScript code examples, update code block language, and refine descriptions in a11y debugging skill documentation. ([ChromeDevTools#1009](ChromeDevTools#1009)) ([5cedcaa](ChromeDevTools@5cedcaa)) * types for JSON output of IssueFormatter ([ChromeDevTools#1007](ChromeDevTools#1007)) ([9ef4479](ChromeDevTools@9ef4479)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
Adds storage-isolated browser contexts via an optional
isolatedContextparameter on thenew_pagetool, following the simplified design proposed by @OrKoN in #926.Pages created with the same
isolatedContextname share cookies, localStorage, and storage. Pages in different isolated contexts (or the default context) are fully isolated — ideal for testing multi-user real-time features like chat, notifications, or collaborative editing.Changes
new_pagetoolisolatedContext: stringparameterBrowserContextand opens a page in itMcpContext#isolatedContextsMap: LLM-provided names → PuppeteerBrowserContextinstances#pageToIsolatedContextNameWeakMap: GC-safe page → context name reverse lookupisolated-context-1,isolated-context-2, etc.getIsolatedContextName(page): returns the isolated context name for a page (used by response formatting)page.browserContext()used for context membership detection (no custom target event forwarding needed)dispose()orclosePage()— either the entire browser is closed or we disconnect without destroying stateMcpResponseisolatedContext=${name}labels (both text and structured JSON output)ToolDefinitionContextinterface extended withgetIsolatedContextName(page)methodWhat's NOT included (by design)
TargetEventEmitter: Puppeteer forwards target events fromBrowserContext→Browserinternallydispose()or page close, per maintainer guidanceabout:blankcleanup: Default context and isolated contexts coexist side-by-sideExample
Pages in different isolated contexts have fully independent cookies, localStorage, IndexedDB, and WebSocket connections.
Tests
isolatedContextfeature intests/tools/pages.test.tsCloses #926