From 9cacce3bf7c42b78a86b18237deddd19ec2f92d5 Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Thu, 16 Jul 2026 03:28:29 +0700 Subject: [PATCH 1/3] fix: report the tab id in the get_tab_id response text get_tab_id only wrote the tab id into structuredContent, which the tool handler attaches to the result exclusively when --experimental-structured-content is set. The tool itself is gated behind --experimental-interop-tools, so enabling just the interop tools returned an empty response for every page. --- src/tools/pages.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/pages.ts b/src/tools/pages.ts index dae8aa862..f862131ed 100644 --- a/src/tools/pages.ts +++ b/src/tools/pages.ts @@ -408,6 +408,12 @@ export const getTabId = definePageTool({ handler: async (request, response, context) => { const page = context.getPageById(request.params.pageId); const tabId = (page.pptrPage as unknown as CdpPage)._tabId; + // Puppeteer returns an empty string when the tab id is not known. + if (!tabId) { + response.appendResponseLine('The tab ID is not available for this page'); + return; + } response.setTabId(tabId); + response.appendResponseLine(`Tab ID: ${tabId}`); }, }); From 5b96d7c41ad544e7770c6f674201599eb578000b Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Thu, 16 Jul 2026 03:28:29 +0700 Subject: [PATCH 2/3] test: cover get_tab_id output without structured content The existing test stubbed _tabId and called response.handle() directly, bypassing the tool handler where structured content is gated, so it passed while the tool returned nothing in practice. --- tests/tools/pages.test.ts | 64 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/tests/tools/pages.test.ts b/tests/tools/pages.test.ts index f17ff61ca..63966e020 100644 --- a/tests/tools/pages.test.ts +++ b/tests/tools/pages.test.ts @@ -12,6 +12,8 @@ import type {Dialog} from 'puppeteer-core'; import sinon from 'sinon'; import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; +import {parseArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; +import {ToolHandler} from '../../src/ToolHandler.js'; import { listPages, newPage, @@ -22,6 +24,8 @@ import { handleDialog, getTabId, } from '../../src/tools/pages.js'; +import type {DefinedPageTool} from '../../src/tools/ToolDefinition.js'; +import {Mutex} from '../../src/utils/Mutex.js'; import {assertNoServiceWorkerReported, html, withMcpContext} from '../utils.js'; const EXTENSION_SW_PATH = path.join( @@ -1282,18 +1286,68 @@ describe('pages', () => { await withMcpContext(async (response, context) => { const page = context.getSelectedMcpPage().pptrPage; // @ts-expect-error _tabId is internal. - assert.ok(typeof page._tabId === 'string'); - // @ts-expect-error _tabId is internal. - page._tabId = 'test-tab-id'; + const tabId = page._tabId as string; + assert.ok(tabId); await getTabId.handler( {params: {pageId: 1}, page: context.getSelectedMcpPage()}, response, context, ); const result = await response.handle('get_tab_id', context); + // @ts-expect-error structuredContent is not typed. + assert.strictEqual(result.structuredContent.tabId, tabId); + assert.deepStrictEqual(response.responseLines, [`Tab ID: ${tabId}`]); + }); + }); + + it('reports the tab id when structured content is disabled', async () => { + await withMcpContext(async (_response, context) => { + const page = context.getSelectedMcpPage().pptrPage; // @ts-expect-error _tabId is internal. - assert.strictEqual(result.structuredContent.tabId, 'test-tab-id'); - assert.deepStrictEqual(response.responseLines, []); + const tabId = page._tabId as string; + assert.ok(tabId); + const serverArgs = parseArguments( + '1.0.0', + ['node', 'script.js', '--experimentalInteropTools'], + {CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'}, + ); + const toolHandler = new ToolHandler( + getTabId as unknown as DefinedPageTool, + serverArgs, + async () => context, + new Mutex(), + ); + + const result = await toolHandler.handle({pageId: 1}); + + assert.strictEqual(result.isError, undefined); + assert.strictEqual(result.structuredContent, undefined); + const text = result.content + .map(part => (part.type === 'text' ? part.text : '')) + .join('\n'); + assert.ok( + text.includes(tabId), + `expected the tab id in the response, got: ${text}`, + ); + }); + }); + + it('reports when the tab id is not available', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedMcpPage().pptrPage; + // @ts-expect-error _tabId is internal. + page._tabId = ''; + await getTabId.handler( + {params: {pageId: 1}, page: context.getSelectedMcpPage()}, + response, + context, + ); + const result = await response.handle('get_tab_id', context); + // @ts-expect-error structuredContent is not typed. + assert.strictEqual(result.structuredContent.tabId, undefined); + assert.deepStrictEqual(response.responseLines, [ + 'The tab ID is not available for this page', + ]); }); }); }); From b762a947ed5dcdd2fdbe39a94b210b3dddf74cef Mon Sep 17 00:00:00 2001 From: serhiizghama Date: Fri, 17 Jul 2026 03:57:16 +0700 Subject: [PATCH 3/3] test: simplify get_tab_id no-structured-content test via withMcpContext args --- tests/tools/pages.test.ts | 56 ++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/tests/tools/pages.test.ts b/tests/tools/pages.test.ts index 63966e020..93eef8dd6 100644 --- a/tests/tools/pages.test.ts +++ b/tests/tools/pages.test.ts @@ -12,8 +12,6 @@ import type {Dialog} from 'puppeteer-core'; import sinon from 'sinon'; import type {ParsedArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; -import {parseArguments} from '../../src/bin/chrome-devtools-mcp-cli-options.js'; -import {ToolHandler} from '../../src/ToolHandler.js'; import { listPages, newPage, @@ -24,8 +22,6 @@ import { handleDialog, getTabId, } from '../../src/tools/pages.js'; -import type {DefinedPageTool} from '../../src/tools/ToolDefinition.js'; -import {Mutex} from '../../src/utils/Mutex.js'; import {assertNoServiceWorkerReported, html, withMcpContext} from '../utils.js'; const EXTENSION_SW_PATH = path.join( @@ -1301,35 +1297,29 @@ describe('pages', () => { }); it('reports the tab id when structured content is disabled', async () => { - await withMcpContext(async (_response, context) => { - const page = context.getSelectedMcpPage().pptrPage; - // @ts-expect-error _tabId is internal. - const tabId = page._tabId as string; - assert.ok(tabId); - const serverArgs = parseArguments( - '1.0.0', - ['node', 'script.js', '--experimentalInteropTools'], - {CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: 'true'}, - ); - const toolHandler = new ToolHandler( - getTabId as unknown as DefinedPageTool, - serverArgs, - async () => context, - new Mutex(), - ); - - const result = await toolHandler.handle({pageId: 1}); - - assert.strictEqual(result.isError, undefined); - assert.strictEqual(result.structuredContent, undefined); - const text = result.content - .map(part => (part.type === 'text' ? part.text : '')) - .join('\n'); - assert.ok( - text.includes(tabId), - `expected the tab id in the response, got: ${text}`, - ); - }); + await withMcpContext( + async (response, context) => { + const page = context.getSelectedMcpPage().pptrPage; + // @ts-expect-error _tabId is internal. + const tabId = page._tabId as string; + assert.ok(tabId); + await getTabId.handler( + {params: {pageId: 1}, page: context.getSelectedMcpPage()}, + response, + context, + ); + const result = await response.handle('get_tab_id', context); + const text = result.content + .map(part => (part.type === 'text' ? part.text : '')) + .join('\n'); + assert.ok( + text.includes(tabId), + `expected the tab id in the response, got: ${text}`, + ); + }, + {}, + {experimentalInteropTools: true, experimentalStructuredContent: false}, + ); }); it('reports when the tab id is not available', async () => {