Repository navigation
Include the CDP targetId in list_pages output #2366
Description
Activity
- changed the title
[-]Include the CDP targetId in list_pages output[/-][+]Include the CDP `targetId` in `list_pages` output[/+]on Jul 14, 2026 Thanks for the feature request but we do not plan to expose targetId via list_pages as for most users it has no utility. Could you use the experimental get_tab_id instead https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/src/tools/pages.ts#L392?
Thanks, that makes sense – for the common case I agree it's not worth putting on
list_pages.I did give
get_tab_ida try (with--experimental-interop-tools), but over--browserUrlit comes back empty for every page – notabIdin the response. Reproduced against both plain headless Chrome 150 and our Electron app (all tabs empty, selected or not). Happy to share the repro if it's useful.So for a
--browserUrlsetup it doesn't quite cover the mapping – I'll keep matchinglist_pagesagainstGET /jsonon our side, which works fine, and close the PR. Thanks for the quick look!It's not in active use and uses internal Puppeteer APIs so it might have regressed (I am able to repro with and without browser URL).
Interesting: I am not able to reproduce it in our test, there is an non-empty target ID.
FWIW, a page I drive with puppeteer directly always has a populated
_tabId– whether I launch or connect, bundled build included. It only comes back empty through the running server'sget_tab_id, in both launch and--browserUrlmodes (onmaintoo). So it doesn't seem to track the transport or launch/connect – looks specific to the page objects the server ends up with. Couldn't pin the exact spot, but happy to share the repro if it helps.Pinned it down – it's not the page objects, and
_tabIditself is fine. The value just never reaches the client.get_tab_idonly writes it intostructuredContent(McpResponse:structuredContent.tabId = this.#tabId) and never into the text response, andToolHandlerdropsstructuredContententirely unless--experimentalStructuredContentis set. So the tool gates on--experimentalInteropToolsbut its only way out gates on a different flag – with interop alone it's always empty, and the text response is""either way (checked on 1.6.0).That matches your repro through the server, and would explain why the test is the odd one out: reading
structuredContentdirectly, the id is there and non-empty, while anything going through the server on interop alone never gets it emitted. It'd also cover why it hasn't come up otherwise –chrome-devtools.jshas--experimentalStructuredContentindefaultArgs, so the flag is always on viachrome-devtools start.Happy to PR a fix – either pushing the id into the text response like the other fields, or an
implies: 'experimentalStructuredContent'onexperimentalInteropToolsso it fails loudly instead. Or I can move it to its own issue if you'd rather track it separately.FWIW, one thing that came up on the way:
_tabIdis the tab target, whileGET /jsononly liststype: page– for the same tab I get a tab target id out ofget_tab_idand a different page target id out of/json, neither showing up in the other's list. So even fixed it wouldn't be a join key against/json– no bearing on this issue, just noting it. Staying on the/jsonmatching either way.You can pass
?for_tab=truein the /json query to get tab targets. Now I recall that I did not add it to the test response to avoid parsing out of the text data but I think it makes sense to include in the text output.Thanks –
for_tabworks, one catch: it has to be value-less./json?for_tabreturns thetype: tabtargets, and their ids match whatget_tab_idreturns (verified same-session), while?for_tab=trueis silently ignored (Chrome 150 here). Didn't know about that param – so the tab id is joinable after all. Our matching is cross-restart, where ids renew anyway, so we'll stay on title/url.Text output PR: #2381.
- added a commit that references this issue
on Jul 20, 2026
Is your feature request related to a problem? Please describe.
I run a thin stdio proxy in front of
chrome-devtools-mcpthat keeps page ids stable across restarts of the browser it drives (a dev app that gets re-exec'd at the same CDP port). The server reconnects fine on 1.6.0 and #2345 keeps ids unique, but a tab still gets a new id after a restart, so the proxy rewriteslist_pagesto hold one id per tab.To do that it has to map each
list_pagesentry back to its CDP target, and there's no shared id to join on –list_pagesgivestitleandurlbut not the CDPtargetId. So the proxy fetchesGET /jsonon the side and matches each entry by title, then url (consume-once). It's the most fragile thing the proxy does. Tabs that share a url are separable only by title – several of my tabs render the identicaldata:loader – so when titles also collide the match comes down to positional luck. And it's a second, independent read of browser state, so a tab opening or navigating between the/jsonfetch and the server's own view throws the listing off.Describe the solution you'd like
Put each page's CDP
targetIdinlist_pagesoutput – ideally a field on thestructuredContent.pages[]entries (and/or the text line). Then the mapping is justentry.targetId, with no/jsonround-trip to match up.It's already on hand server-side (
PageCollectorreads it per page today), so it's a one-field addition tocreateStructuredPage()that leaves the existing fields alone – same shape as #2156, which addedtitle. Happy to send the PR.Describe alternatives you've considered
GET /json+ title/url heuristic – works today, but is the fragile part above/json(or opening a separate CDP session) and re-matching, with the same fragility and a second source of truth to keep in syncAdditional context
targetIdis stable per tab within a session and renews when the browser process restarts, same lifecycle as the underlying CDP id – I'm only after exposing it, not changing that.Related: #2156 (added
title), #2345 (unique ids across reconnects), #2304 (stale-listing selection retarget).