Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/tools/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
},
});
64 changes: 59 additions & 5 deletions tests/tools/pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -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) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You should be able to pass

withMcpContext(cb(), {
  experimentalInteropTools: true, 
  experimentalStructuredContent: false,
})

That will remove the need for the setup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — dropped the ToolHandler setup and pass the flags through withMcpContext instead. Went with the third args param since that's where the ParsedArguments overrides land.

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',
]);
});
});
});
Expand Down