文件历史

提交图

42 次代码提交

作者 SHA1 备注 提交日期
Nikolay Vitkov cf4ec31d82 Fix 2026-07-20 10:07:29 +00:00
Thomas Bachem 3e8d92245c fix: keep page ids unique across browser reconnects (#2345)
Closes #2339

Reconnecting after a browser restart builds a fresh `McpContext`, which
restarted the page id counter at 1 – ids from before the restart
silently resolved to unrelated pages of the new browser (repro in
#2339).

Two changes:

- The new context continues the id counter where the previous one left
off (`startingPageId` option), so a stale id now fails with the existing
"No page found" error and the agent re-lists
- The first response after a reconnect carries a one-time note ("the
browser was restarted or reconnected since the last call. Page ids have
changed..."), like the #2308 fallback note. Since tool errors run
through the same response formatting, the note shows up together with
the very error the stale id produces

Verified against the #2339 repro: the stale `select_page` now returns
the note plus "No page found", and the next listing shows the new
browser's pages under fresh ids.

One thing I noticed but left alone: The replaced context isn't
`dispose()`d on reconnect – pre-existing, and everything it holds is
tied to the dead browser anyway. Happy to add that here if you'd like.
2026-07-13 08:58:13 +00:00
Alex Rudenko 68cfce2bcb refactor: clean up page snapshot generation (#2348)
- remove obsolete devtools page detection step
- move isolated context processing out of the browser pages fetching
- move filtering of pages out of getPages(). That should just be a
getter.
2026-07-13 08:08:59 +00:00
Alex Rudenko c53c1ec4c9 refactor: use response page in formatting (#2349) 2026-07-13 07:55:37 +00:00
Alex Rudenko 58ba174273 refactor: move remaining McpContext getters (#2342)
Moves various getters to McpPage.
2026-07-10 12:46:01 +00:00
Thomas Bachem eb049513d2 fix: resolve page ids only among listed pages (#2332)
Follow-up to #2304, and to #2328 which fixed the symptom.

After #2333, `getPages()` – what `list_pages` presents – is a filtered
view over `#mcpPages` that excludes `devtools://` frontends (unless
`experimentalDevToolsDebugging` is set; they enter `#mcpPages` via
`handleDevToolsAsPage`). `getPageById()` still searches all of
`#mcpPages`, though – so `select_page`, and every other tool that takes
a `pageId`, can target a page `list_pages` never showed.

#2328 stopped that from silently stealing a still-open selection. This
goes to the root: `getPageById()` now resolves through `getPages()`, so
an unlisted page isn't targetable in the first place. Page ids only ever
reach the client through the listing, so an unlisted id has no
legitimate source. `experimentalDevToolsDebugging` is unaffected – the
listing already includes devtools frontends there.

All five callers (`select_page`, `close_page`, `evaluate_script`,
`get_tab_id`, and the generic `pageId` handler) benefit uniformly. Also
trims the now-impossible example from the fallback comment – an unlisted
`devtools://` page can no longer be selected.

Test: with DevTools open, the frontend page is tracked but unlisted; the
new test asserts no id outside `getPages()` resolves through
`getPageById()`.

Refs: #2304
2026-07-10 11:03:08 +00:00
Alex Rudenko 5b33debf1a refactor: clean up McpContext getters (#2340) 2026-07-10 07:46:31 +00:00
Alex Rudenko 9cd734b0a3 refactor: clean up more of the context interface (#2335)
- removed redundant page getters
2026-07-09 13:50:48 +00:00
Alex Rudenko 16db01f79c refactor: clean up page management (#2333)
- remove `#pages` and Puppeteer page getters
- rename methods for clarity
2026-07-09 11:42:11 +00:00
Alex Rudenko 9bc61b43d6 refactor: make collectors work per page (#2324) 2026-07-09 10:03:03 +00:00
Thomas Bachem c645eee875 fix: keep a still-open selected page instead of falling back to the first page (#2328)
Follow-up to #2304.

`createPagesSnapshot()` reselects `#pages[0]` whenever the selected page
is missing from `#pages`, even when that page is still open. So a live
selection can be silently swapped, not only a closed one.

The case that hit us is a `devtools://` page:

- the server connects with `handleDevToolsAsPage: true`
(`src/browser.ts`), so DevTools frontends are pages and land in
`#mcpPages`
- `#pages` filters `devtools://` out (`src/McpContext.ts`, unless
`experimentalDevToolsDebugging` is set)
- `getPageById()` (used by `select_page`) reads `#mcpPages`, not
`#pages`, so a devtools page can be selected even though `list_pages`
never lists it
- the next snapshot finds it missing from `#pages` and, because the
fallback keys on `#pages` membership rather than `isClosed()`, reselects
`#pages[0]` with the page still open (`isClosed() === false`)

This reproduces on a normal Chrome (details and a repro in #2304).

This change gates the fallback on `isClosed()`, as proposed in #2304. A
still-open selection is kept; a genuinely closed page still
auto-advances, so the browser-like behavior for closed tabs is
unchanged.

One consequence: the "is no longer listed" wording added in #2308
becomes unreachable, since a live page missing from the list no longer
triggers a fallback. I left it in place to keep this diff focused, but
I'm happy to simplify it here or in a follow-up.

The unit test that covered the missing-but-open case now asserts the
selection is retained.

Refs: #2304
2026-07-09 07:09:11 +00:00
herdiyanitdev 6e56c028cf feat: support --allow-unrestricted-paths configuration (#2296)
## Summary

validatePath() in McpContext returned immediately, with no restriction
at all, whenever roots() returned undefined. roots() only returns
undefined when the connecting MCP client never negotiates the optional
roots capability during initialize, which any minimal client can trigger
simply by omitting it from its declared capabilities.

Since roots() already always appends the OS temp directory to whatever
explicit roots are configured, this change makes it return that same
default (temp directory only) instead of undefined when no roots have
been set. This removes the early return in validatePath() entirely, so
path validation now runs unconditionally rather than being conditional
on whether the connecting client happened to negotiate a capability it
was never required to declare per the MCP spec.


Any filePath-accepting tool (take_screenshot, saveFile, and the
performance/Lighthouse export tools that route through the same check)
had its only path-traversal guard silently disabled for the lifetime of
a connection whenever the client omitted the optional roots capability.
Since this server is designed to let an LLM drive a browser, and browsed
page content is not trusted input, this meant a client that simply
doesn't implement roots (a plausible, non-adversarial default for
lightweight or custom MCP clients) removed the only boundary preventing
the connected agent from writing to any path the process can reach.


Added a test that exercises the actual default state of roots (never
calling setRoots()) directly, since the existing tests always call
setRoots(), even with an empty array, before validating. Verified
locally with a minimal MCP client that declares no capabilities: before
this change, take_screenshot with a filePath outside any root wrote a
real file to an arbitrary path with no error; after this change, the
same call is rejected with the existing Access denied error. Also
verified that a client that does declare roots is unaffected, and that
writes to the OS temp directory continue to succeed with no roots
negotiated, matching prior behavior for that path.
2026-07-08 16:49:31 +00:00
Thomas Bachem 2c16ac311b fix: report when the selected page was auto-replaced by the fallback (#2308)
Follow-up to #2304 (the "note" part discussed there).

When the selected page disappears from the page list,
`createPagesSnapshot()` silently re-selects the first page. The agent
gets no signal: if its next call is `list_pages` (which is what the
closed-page error message recommends), the listing already shows the new
selection and every subsequent tool call runs against a page the agent
never picked.

This PR records the automatic fallback and surfaces it as a one-line
note in the pages section of the same response:

```
## Pages
Note: the previously selected page was closed. Page 1 is now selected.
1: about:blank [selected]
```

For a selected page that is missing from the list without being closed,
the note reads "is no longer listed" instead. If the expectation from
#2304 holds (a page stays listed as long as it is not closed), that
wording never renders; if the transient case discussed there does occur
in the wild, the note will make it visible.

The fallback behavior itself is unchanged (as discussed in #2304, closed
tabs keep the browser-like auto-selection). No note is emitted on first
connect, when nothing was selected before.

Tests: two unit tests for the fallback bookkeeping (closed page, regular
selection), one for the missing-but-open case via a stubbed page list,
and the one affected snapshot updated (`close_page` now includes the
note).

Refs: #2304
2026-07-08 07:08:26 +00:00
Nikolay Vitkov 6a9466378c fix: respecte allow/block list in loadResouce (#2254)
Fixes https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/2218
Closes https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/2225
2026-06-24 13:16:34 +00:00
Nikolay Vitkov 834b259eb2 chore: expose loadResouce HostBinding (#2194)
This makes sure that if any resource that is loaded via the HostBinding
will be correctly checked agains the roots object.
2026-06-09 15:11:39 +00:00
Alex Rudenko f8fb2a9236 test: drop node20 code in tests (#2146) 2026-05-27 11:12:22 +00:00
Alex Rudenko 176eb69513 fix: use realpath for MCP roots validation (#2127) 2026-05-26 13:04:43 +00:00
Alex Rudenko a90378adf3 fix: always allow tmpdir access with client roots (#1984)
This PR changes the implementation to allow access to tmpdir even if the
client is not configured it explicitly because several tools default to
the tmpdir for outputs. Many clients like gemini-cli/claude do not
configure the tmp dir as a root by default.
2026-04-30 12:25:35 +00:00
Alex Rudenko def53ddf1d feat: support MCP client roots feature (#1945)
This PR implements
https://modelcontextprotocol.io/specification/2025-11-25/client/roots If
client specifies roots, all reads and writes to the file system
originating in tool calls will denied (including tmp files). The client
specified empty list of roots, all filesystem access will be restricted.

Closes https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/1860

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Piotr Paulski <piotrpaulski@chromium.org>
2026-04-27 11:55:30 +00:00
Wolfgang Beyer e046c23563 chore: in-page tool output: stash DOM elements and preprocess for serialization (#1877)
Improvements for handling in-page tool responses. In order to
successfully pass an in-page tool response from the page context to the
MCP server, the response needs to be serializable. The code walks the
response object an performs the following changes:

- DOM elements are stashed onto the window object and replaced with an
ID. On the MCP server side this ID is used to map back to the
corresponding UID in the page snapshot generated from the accessibility
tree.
- Circular references are replaced with a string.
- Class instances (which can be complex or non-serializable) are
replaced with a string.
- Functions are replaced with a string.

If the in-page tool response contains DOM elements which are not part of
the page snapshot, a new snapshot is created add the missing elements
are added explicitly.
2026-04-22 14:51:09 +00:00
Nikolay Vitkov d8a4398fb8 test: remove waiting for DevTools page (#1793) 2026-04-02 05:20:40 +00:00
Tolgahan Demirbaş c7c8f50f80 refactor: move waitForEventsAfterAction to McpPage (#1780)
Splits out from #1244 per review feedback.

'waitForEventsAfterAction' previously lived in 'McpContext' and always
used the selected page's CPU/network throttling settings. With pageId
routing, a tool can target a different page than the selected one,
meaning wrong throttling multipliers were applied.

Moving the method to 'McpPage' fixes this: each tool now calls
'page.waitForEventsAfterAction(...)' and gets the correct page's
emulation settings.

'getNetworkMultiplierFromString' is extracted to 'WaitForHelper.ts' to
avoid a circular import (McpContext → McpPage already exists).

Unblocks #1777.
2026-04-01 12:02:34 +00:00
Alex Rudenko 9628dabcb4 refactor: clean up more of the context getters (#1062)
- moves more code to the McpPage
- deleted Context specific tests
2026-02-27 12:53:20 +00:00
Alex Rudenko 4cb5a17b57 refactor: remove page passing via context (#1061)
- removes several redundant getters on the context.
- removes passing page instances via context.
2026-02-27 12:08:10 +00:00
Alex Rudenko 40c241bbfc refactor: move dialog handling to McpPage (#1059)
- renamed getSelectedPage to getSelectedPptrPage
- removes getSelectedPptrPage from tool interfaces
- moves dialog handling to McpPage
- makes responses to be optionally McpPage-scoped
2026-02-27 11:49:43 +01:00
Alex Rudenko 302e5a0419 refactor: consistently use McpPage in tools (#1057)
Follow-up to
https://github.com/ChromeDevTools/chrome-devtools-mcp/commit/caf601a32832bb87cfac801a6bbeacb87508412f

The work is not complete and as a follow-up I will handle other Context
methods and moved whatever is relevant to the McpPage. Also, duplicate
getters would go away.
2026-02-27 09:41:08 +00:00
Stanislav Publika caf601a328 feat: add pageId routing for parallel multi-agent workflows (#1022)
## 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
2026-02-26 11:39:38 +00:00
Alex Rudenko bc3c40e8f9 refactor: move emulation settings to context (#1000)
- reduces boilerplate 
- allows easily to restore emulation settings

This is in preparation for the integration with Lighthouse.
2026-02-20 11:36:14 +00:00
Alex Rudenko c9691c6250 test: fix missing sinon.restore (#989) 2026-02-18 15:39:30 +00:00
Alex Rudenko a6cd2cd3f2 fix: re-use node ids across snapshots (#814)
previously, Chrome DevTools MCP was strictly requiring the client to use
the latest snapshot. This change updates the behavior to be more
lenient. Previously issued uids will be recognized if the underlying
backend Id (loaderId + backendNodeId) still exists.

Related https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/726
2026-01-22 14:25:39 +00:00
Alex Rudenko fbd22ce869 chore: structured performance responses (#809)
This PR moves formatting out of the tools and to the McpResponse.
Drive-by: clears up past trace results.
2026-01-22 10:01:59 +00:00
Alex Rudenko 6d0e4cab28 feat: support filePath for network request and response bodies (#795)
Closes https://github.com/ChromeDevTools/chrome-devtools-mcp/issues/377
2026-01-19 12:44:59 +00:00
Alex Rudenko 755eeef3af chore: support structured network (#774)
This PR adds a structured variant of the network responses and refactors
the code accordingly.
2026-01-15 11:59:00 +00:00
Simon Zünd 8e90a92780 chore: split withBrowser into withBrowser and withMcpContext (#620)
I split this PR off from my "create one DevTools universe per page" PR
in preparation. This allows tests to re-use browser instances without
creating an `McpContext`.

Drive-by: Move mocked browser/page into utils.ts.
2025-11-28 07:52:52 +01:00
zyzyzyryxy 94752ffade fix: prevent dropping license notices on some files when publishing (#604)
This PR prevents license notices being dropped when creating package for
publication.

This can happen when first import in the file is type-only import that
gets removed during build. When there is no empty line between the
license block comment and such import, the comment is treated as related
to the import and gets removed alongside it.
Adding an empty line between copyright notice and the import fixes the
issue.

Co-authored-by: Piotr Paulski <piotrpaulski@chromium.org>
2025-11-24 12:38:30 +00:00
Nikolay Vitkov 376f57c972 test: use html helper (#478) 2025-10-27 12:32:47 +00:00
Nikolay Vitkov 73be1b4424 build: extract bundled in a separate config (#474) 2025-10-27 12:30:33 +01:00
Alex Rudenko 1560ff23ca refactor: detect DevTools page for each page (#467) 2025-10-27 09:53:34 +00:00
Nikolay Vitkov 15277541d8 chore: add additional EsLint rule (#147) 2025-09-25 17:20:01 +02:00
Nikolay Vitkov c509c64576 fix: increase timeouts in case of Emulation (#71) 2025-09-22 18:13:49 +02:00
Jack Franklin 21e175b862 feat: add performance_analyze_insight tool. (#42) 2025-09-17 12:25:40 +00:00
Alex Rudenko 014a8bc52e feat: validate uids (#37)
- generate snapshot IDs.
- change uid to be a string.
- validate that uid is not coming from a stale snapshot.
2025-09-16 11:54:35 +00:00