Skip to content

feat: add pageId routing for parallel multi-agent workflows#1022

Merged
OrKoN merged 5 commits intoChromeDevTools:mainfrom
passtas:feat/isolated-context-page-routing
Feb 26, 2026
Merged

feat: add pageId routing for parallel multi-agent workflows#1022
OrKoN merged 5 commits intoChromeDevTools:mainfrom
passtas:feat/isolated-context-page-routing

Conversation

@passtas
Copy link
Copy Markdown
Contributor

@passtas passtas commented Feb 23, 2026

Summary

Adds optional pageId routing to page-scoped tools, gated behind --experimental-page-id-routing. When enabled, multi-agent callers can target a specific page without relying on global selection state. Fully backward-compatible: without the flag, behavior is unchanged.

Key changes

  • pageScoped annotation: tools declare pageScoped: true; the server merges pageId into their schema at registration time (when the flag is on)
  • McpPage wrapper: consolidates per-page state (numeric id, isolated context name, focus tracking) into a single class
  • Request-scoped page routing: resolvePageById() resolves the target page, setRequestPage() threads it through the handler so tools like getSelectedPage() see the right page
  • assertPageIsFocused: keyboard/input tools validate that the target page holds browser focus, returning an actionable error ("call select_page first") instead of silently dispatching to the wrong page
  • --experimental-page-id-routing CLI flag (hidden): gates schema injection and request-scoped routing so the feature can be tested before graduating
  • Eval scenarios: page_id_routing_test and page_focus_keyboard_test with serverArgs support in the eval harness

Addresses #1019

@passtas passtas force-pushed the feat/isolated-context-page-routing branch from 3b4e2f4 to 50005b9 Compare February 23, 2026 15:59
@passtas passtas marked this pull request as draft February 23, 2026 16:02
@passtas passtas force-pushed the feat/isolated-context-page-routing branch 13 times, most recently from 765a9d5 to 6341efd Compare February 23, 2026 18:59
@passtas passtas marked this pull request as ready for review February 23, 2026 19:00
@OrKoN OrKoN self-requested a review February 24, 2026 08:12
@passtas passtas force-pushed the feat/isolated-context-page-routing branch from 6341efd to 166c3ab Compare February 24, 2026 08:48
Copy link
Copy Markdown
Collaborator

@OrKoN OrKoN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the PR does not extend the focused page emulation and the dialog handling for all pages. Without it, tools might hang and not work as expected on background tabs.

@passtas
Copy link
Copy Markdown
Contributor Author

passtas commented Feb 24, 2026

@OrKoN, I prototyped fixes for both and wanted to validate the approach before going further (see 8edf6e8).

Dialog: Replaced the singleton #dialog with #pageDialogs = new Map<Page, Dialog>(). Handlers are attached per-page via a #setupPage() method called when pages are first discovered. getDialog and clearDialog take an optional page param, defaulting to selected page for backward compat.

handle_dialog now resolves page via pageIdSchema.

Focus: Added #focusedPagePerContext = new Map<BrowserContext, Page>(). The issue is that with parallel agents interleaving selectPage() calls, #selectedPage bounces between contexts so the wrong page gets defocused (or none does). Now selectPage() looks up the last focused page per-context, so it always defocuses the correct same-context page. Entries are cleaned up in closePage().

Both follow the same pattern: singleton field becomes a Map keyed by Page or BrowserContext, existing API stays backward-compatible via optional params, cleanup on page close.

Before I extend this further, wanted your take on introducing these Maps. The next candidate would be #textSnapshot which has the same singleton problem (one agent's uid references go stale when another agent snapshots a different page). That's a bigger change though, so I'd rather confirm you're good with the direction on these simpler cases first.

@OrKoN
Copy link
Copy Markdown
Collaborator

OrKoN commented Feb 24, 2026

I will need to think about it more but I think there could be the following opportunities:

  1. instead of adding pageId schema in each tool definition file, we could have an annotation saying that a tool is page-scoped and then add the schema centrally. We can also avoid looking up the right page in the tool logic by passing request.page centrally.

  2. I think we might be at the point where we have outgrown McpContext. Instead we would need to introduce an McpPage wrapper over Puppeteer's page to hold page related data instead of adding maps and simplify the McpContext interface by moving some actions to McpPage and removing the need for so many maps.

  3. tools that already accept a uid or any other id should not need a page id, instead we need to make sure that IDs are unique across all pages.

@OrKoN
Copy link
Copy Markdown
Collaborator

OrKoN commented Feb 24, 2026

Another thing to keep in mind is that although performance tracing tools are scoped to a page it is not possible to start more than one tracing in the entire browser at the same time (even if pages are different).

@passtas passtas force-pushed the feat/isolated-context-page-routing branch 2 times, most recently from 2295020 to d35bbf9 Compare February 24, 2026 20:10
@passtas passtas requested a review from OrKoN February 24, 2026 20:13
Comment thread src/McpContext.ts
Comment thread src/tools/ToolDefinition.ts Outdated
Comment thread src/tools/ToolDefinition.ts
Comment thread src/main.ts Outdated
Comment thread src/tools/input.ts Outdated
@passtas passtas force-pushed the feat/isolated-context-page-routing branch from 1257f02 to 84d18b1 Compare February 25, 2026 14:38
@passtas passtas requested a review from OrKoN February 25, 2026 14:45
@passtas passtas force-pushed the feat/isolated-context-page-routing branch from 84d18b1 to 65d3691 Compare February 25, 2026 15:03
Comment thread src/McpContext.ts
Comment thread src/main.ts Outdated
@passtas passtas force-pushed the feat/isolated-context-page-routing branch from 65d3691 to d202091 Compare February 26, 2026 10:05
@OrKoN
Copy link
Copy Markdown
Collaborator

OrKoN commented Feb 26, 2026

I think there is a merge conflict caused by a8bf3e5 To solve changes from main.ts should be moved to server.ts

@passtas passtas force-pushed the feat/isolated-context-page-routing branch from d202091 to 30c3daf Compare February 26, 2026 10:36
Add optional `isolatedContext` parameter to all page-dependent tools so
parallel agents can resolve pages by context name instead of relying on
the global selected-page pointer.

When an agent creates a page with `new_page(isolatedContext: "my-agent")`,
all subsequent tool calls can pass `isolatedContext: "my-agent"` to
operate on the correct page without race conditions from other agents
calling `select_page` concurrently.

McpContext tracks per-context selected pages and resolvePageByContext()
looks up the right page by context name.  When the parameter is omitted,
tools fall back to getSelectedPage() (fully backward compatible).

Updated tools: take_screenshot, take_snapshot, wait_for, navigate_page,
resize_page, emulate, click_at, fill, fill_form, upload_file, press_key,
evaluate_script, performance_start_trace, performance_stop_trace,
screencast_start.
Replace the isolatedContext-based page resolution with the more general
pageId parameter. The isolatedContext approach only worked when agents
used different browser contexts. pageId works for any multi-page
scenario, uses an already-existing concept (page IDs), and does not
depend on isolated contexts.

- Rename isolatedContextSchema to pageIdSchema (string to number)
- Replace resolvePageByContext() with resolvePageById() in McpContext
- Remove per-context page tracking (#contextSelectedPage map)
- Update all tool files to use pageId routing
- Keep isolatedContext on new_page (browser context isolation, not routing)
- Update tests to use pageId-based assertions
…Page wrapper

- Add pageScoped annotation to ToolDefinition; registerTool() auto-injects
  pageIdSchema and resolves the page centrally via resolvePageById()
- defineTool() wrapper guarantees request.page is always populated,
  falling back to getSelectedPage() when pageId is omitted
- Remove manual ...pageIdSchema spread and resolvePageById() calls from
  all 15 page-scoped tool handlers
- Introduce McpPage class consolidating per-page state (dialog, snapshot,
  emulation settings, metadata) that was previously scattered across 8
  Maps/WeakMaps in McpContext
- Store text snapshots per-page on McpPage so parallel agents taking
  snapshots on different pages no longer clobber each other
- Cross-page uid lookup in getElementByUid()/getAXNodeByUid() searches
  all McpPage instances, enabling uid resolution from any page's snapshot
- Update page_id_routing eval to test per-page snapshot isolation and
  cross-page uid click resolution
…ontext cleanup

- Add #requestPage / #resolveTargetPage() on McpContext so data-retrieval
  methods (console, network, emulation getters, DevTools data) automatically
  resolve the correct page for pageScoped tool requests under toolMutex.
- Mark console and network tools pageScoped: true so they receive pageId
  routing like other page-aware tools.
- Add assertPageIsFocused() for keyboard tools (press_key, type_text,
  click_at) to detect when a page is not the active page in its browser
  context and throw an actionable error with the correct pageId.
- Merge getElementByUid and assertUidOnSelectedPage into a single method
  with optional page parameter for scoped search (pageScoped tools) vs
  cross-page search with context-focus validation (uid-based tools).
- Remove unused Context interface methods: resolvePageById,
  resolveCdpElementId, and 6 emulation getters already removed upstream.
- Clean up orphaned #mcpPages and #focusedPagePerContext entries in
  createPagesSnapshot().
- Remove dead code: fillFormElement page parameter made required since all
  callers now provide it.
- Regenerate tool-reference.md.
- Add unit tests for page-scoped getElementByUid and context-focus
  validation, plus eval scenario for assertPageIsFocused recovery flow.
Add --experimental-page-id-routing CLI flag (default false) to control
whether pageId is exposed on page-scoped tools and used for request
routing. When disabled, tools behave as before (select_page workflow).

- Add serverArgs to eval TestScenario interface so individual evals can
  pass CLI flags to the MCP server
- Add TODO for mutable request state refactoring on McpContext
- Add TODO for getSelectedPage removal from Context interface
- Stabilize page_focus_keyboard_test eval prompt and expectations
@passtas passtas force-pushed the feat/isolated-context-page-routing branch from 30c3daf to fcd6e21 Compare February 26, 2026 10:37
@passtas
Copy link
Copy Markdown
Contributor Author

passtas commented Feb 26, 2026

I think there is a merge conflict caused by a8bf3e5 To solve changes from main.ts should be moved to server.ts

Yeah, was just solving that :)

Just FYI: there was one flaky test failure "should should detect open DevTools pages" only on Windows with Node 20. https://github.com/ChromeDevTools/chrome-devtools-mcp/actions/runs/22402585807/job/64955339393?pr=1022

Can you please rerun tests to see if it was flakiness or I actually broke it somehow?

@passtas passtas requested a review from OrKoN February 26, 2026 10:39
@passtas
Copy link
Copy Markdown
Contributor Author

passtas commented Feb 26, 2026

I think there is a merge conflict caused by a8bf3e5 To solve changes from main.ts should be moved to server.ts

Yeah, was just solving that :)

Just FYI: there was one flaky test failure "should should detect open DevTools pages" only on Windows with Node 20. https://github.com/ChromeDevTools/chrome-devtools-mcp/actions/runs/22402585807/job/64955339393?pr=1022

Can you please rerun tests to see if it was flakiness or I actually broke it somehow?

This time it failed on Windows Node 23 :), certainly flaky.

@OrKoN, how would you like to proceed here?

Copy link
Copy Markdown
Collaborator

@OrKoN OrKoN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks!

@OrKoN OrKoN enabled auto-merge February 26, 2026 11:35
@OrKoN OrKoN added this pull request to the merge queue Feb 26, 2026
Merged via the queue into ChromeDevTools:main with commit caf601a Feb 26, 2026
29 of 31 checks passed
github-merge-queue Bot pushed a commit that referenced this pull request Feb 26, 2026
I think we should create proper classes for tool definitions soon.

Follow-up for
#1022
OrKoN pushed a commit that referenced this pull request Mar 5, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.19.0](chrome-devtools-mcp-v0.18.1...chrome-devtools-mcp-v0.19.0)
(2026-03-05)


### 🎉 Features

* add pageId routing for parallel multi-agent workflows
([#1022](#1022))
([caf601a](caf601a)),
closes
[#1019](#1019)
* Add skill which helps with onboarding of the mcp server
([#1083](#1083))
([7273f16](7273f16))
* integrate Lighthouse audits
([#831](#831))
([dfdac26](dfdac26))


### 🛠️ Fixes

* improve error messages around --auto-connect
([#1075](#1075))
([bcb852d](bcb852d))
* improve tool descriptions
([#965](#965))
([bdbbc84](bdbbc84))
* repair broken markdown and extract snippets in a11y-debugging skill
([#1096](#1096))
([adac7c5](adac7c5))
* simplify emulation and script tools
([#1073](#1073))
([e51ba47](e51ba47))
* simplify focus state management
([#1063](#1063))
([f763da2](f763da2))
* tweak lighthouse description
([#1112](#1112))
([5538180](5538180))


### 📄 Documentation

* Adapt a11y skill to utilize Lighthouse
([#1054](#1054))
([21634e6](21634e6))
* add feature release checklist to CONTRIBUTING.md
([#1118](#1118))
([0378457](0378457))
* fix typo in README regarding slim mode
([#1093](#1093))
([92f2c7b](92f2c7b))


### 🏗️ Refactor

* clean up more of the context getters
([#1062](#1062))
([9628dab](9628dab))
* consistently use McpPage in tools
([#1057](#1057))
([302e5a0](302e5a0))
* improve type safety for page scoped tools
([#1051](#1051))
([5f694c6](5f694c6))
* make cdp resolvers use McpPage
([#1060](#1060))
([d6c06c5](d6c06c5))
* move dialog handling to McpPage
([#1059](#1059))
([40c241b](40c241b))
* move server to a separate file
([#1043](#1043))
([a8bf3e5](a8bf3e5))
* remove page passing via context
([#1061](#1061))
([4cb5a17](4cb5a17))
* set defaults to performance trace tool
([#1090](#1090))
([dfa9b79](dfa9b79))
* simplify the response texts
([#1095](#1095))
([cb0079e](cb0079e))
* type-cast as internal CdpPage interface
([#1064](#1064))
([2d5e4fa](2d5e4fa))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
wolfib pushed a commit to wolfib/chrome-devtools-mcp that referenced this pull request Mar 10, 2026
…vTools#1022)

Adds optional `pageId` routing to page-scoped tools, gated behind
`--experimental-page-id-routing`. When enabled, multi-agent callers can
target a specific page without relying on global selection state. Fully
backward-compatible: without the flag, behavior is unchanged.

- **`pageScoped` annotation**: tools declare `pageScoped: true`; the
server merges `pageId` into their schema at registration time (when the
flag is on)
- **`McpPage` wrapper**: consolidates per-page state (numeric id,
isolated context name, focus tracking) into a single class
- **Request-scoped page routing**: `resolvePageById()` resolves the
target page, `setRequestPage()` threads it through the handler so tools
like `getSelectedPage()` see the right page
- **`assertPageIsFocused`**: keyboard/input tools validate that the
target page holds browser focus, returning an actionable error ("call
select_page first") instead of silently dispatching to the wrong page
- **`--experimental-page-id-routing` CLI flag** (hidden): gates schema
injection and request-scoped routing so the feature can be tested before
graduating
- **Eval scenarios**: `page_id_routing_test` and
`page_focus_keyboard_test` with `serverArgs` support in the eval harness

Addresses ChromeDevTools#1019
@pirate
Copy link
Copy Markdown

pirate commented Apr 20, 2026

related: @OrKoN @passtas you may be interested in my chromium CDP feature request to make foreground page tracking easier / avoid needing to maintain internal state in CDP clients to track it: https://issues.chromium.org/issues/497896141

Also in case it helps: At browser-use we made page IDs simply the last 4 characters of targetIds, which helped keep them stable / avoid needing in-memory mappings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants