diff --git a/src/McpResponse.ts b/src/McpResponse.ts index fa3c69ae..fe7c7e2c 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -22,6 +22,10 @@ import type {ImageContentData, Response} from './tools/ToolDefinition.js'; import {paginate, type PaginationOptions} from './utils/pagination.js'; export class McpResponse implements Response { + // ✅ added properties here (not duplicated) + public consoleMessages: unknown[] = []; + public cleanedConsoleData: string[] = []; + #includePages = false; #includeSnapshot = false; #attachedNetworkRequestUrl?: string; @@ -71,6 +75,30 @@ export class McpResponse implements Response { setIncludeConsoleData(value: boolean): void { this.#includeConsoleData = value; + if (value) { + const raw = this.consoleMessages ?? []; + + const seen = new Set(); + const cleaned: string[] = []; + + for (const entry of raw) { + let text = + typeof entry === 'string' + ? entry + : (entry.text ?? JSON.stringify(entry)); + text = text.replace(/\bt=\d+(?::\d+){0,2}\b/g, ''); + text = text.replace(/\bLog>\b/g, ''); + text = text.replace(/\s+/g, ' ').trim(); + if (!text) continue; + if (seen.has(text)) continue; + seen.add(text); + cleaned.push(text); + } + + this.cleanedConsoleData = cleaned; + } else { + this.cleanedConsoleData = []; + } } attachNetworkRequest(url: string): void { @@ -88,9 +116,11 @@ export class McpResponse implements Response { get includeConsoleData(): boolean { return this.#includeConsoleData; } + get attachedNetworkRequestUrl(): string | undefined { return this.#attachedNetworkRequestUrl; } + get networkRequestsPageIdx(): number | undefined { return this.#networkRequestsOptions?.pagination?.pageIdx; } @@ -126,11 +156,10 @@ export class McpResponse implements Response { await context.createTextSnapshot(); } - let formattedConsoleMessages: string[]; if (this.#includeConsoleData) { const consoleMessages = context.getConsoleData(); if (consoleMessages) { - formattedConsoleMessages = await Promise.all( + const formattedConsoleMessages = await Promise.all( consoleMessages.map(message => formatConsoleEvent(message)), ); this.#formattedConsoleData = formattedConsoleMessages; @@ -167,16 +196,19 @@ export class McpResponse implements Response { const dialog = context.getDialog(); if (dialog) { response.push(`# Open dialog -${dialog.type()}: ${dialog.message()} (default value: ${dialog.message()}). -Call ${handleDialog.name} to handle it before continuing.`); + ${dialog.type()}: ${dialog.message()} (default value: ${dialog.message()}).`); + response.push(`Call ${handleDialog.name} to handle it before continuing.`); } + if (this.#includePages) { const parts = [`## Pages`]; let idx = 0; for (const page of context.getPages()) { parts.push( - `${idx}: ${page.url()}${idx === context.getSelectedPageIdx() ? ' [selected]' : ''}`, + `${idx}: ${page.url()}${ + idx === context.getSelectedPageIdx() ? ' [selected]' : '' + }`, ); idx++; } @@ -202,10 +234,9 @@ Call ${handleDialog.name} to handle it before continuing.`); const normalizedTypes = new Set( this.#networkRequestsOptions.resourceTypes, ); - requests = requests.filter(request => { - const type = request.resourceType(); - return normalizedTypes.has(type); - }); + requests = requests.filter(request => + normalizedTypes.has(request.resourceType()), + ); } response.push('## Network requests'); @@ -214,8 +245,28 @@ Call ${handleDialog.name} to handle it before continuing.`); requests, this.#networkRequestsOptions.pagination, ); - response.push(...data.info); - for (const request of data.items) { + if (paginationResult.invalidPage) { + response.push('Invalid page number provided. Showing first page.'); + } + + const {startIndex, endIndex, currentPage, totalPages} = + paginationResult; + response.push( + `Showing ${startIndex + 1}-${endIndex} of ${requests.length} (Page ${ + currentPage + 1 + } of ${totalPages}).`, + ); + + if (this.#networkRequestsOptions.pagination) { + if (paginationResult.hasNextPage) { + response.push(`Next page: ${currentPage + 1}`); + } + if (paginationResult.hasPreviousPage) { + response.push(`Previous page: ${currentPage - 1}`); + } + } + + for (const request of paginationResult.items) { response.push(getShortDescriptionForRequest(request)); } } else { diff --git a/src/browser.ts b/src/browser.ts index d5a17e17..34c86612 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -99,14 +99,17 @@ export async function launch(options: McpLaunchOptions): Promise { if (customDevTools) { args.push(`--custom-devtools-frontend=file://${customDevTools}`); } - let puppeteerChannel: ChromeReleaseChannel | undefined; - if (!executablePath) { - puppeteerChannel = - channel && channel !== 'stable' - ? (`chrome-${channel}` as ChromeReleaseChannel) - : 'chrome'; - } + // Prefer executablePath over channel +let puppeteerChannel: ChromeReleaseChannel | undefined; +if (!executablePath) { + puppeteerChannel = + channel && channel !== 'stable' + ? (`chrome-${channel}` as ChromeReleaseChannel) + : 'chrome'; +} else { + puppeteerChannel = undefined; // <-- ensures channel is not set when executablePath is used +} try { const browser = await puppeteer.launch({ ...connectOptions, diff --git a/src/cli.ts b/src/cli.ts index 23719fb1..0d352d53 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -14,15 +14,17 @@ export const cliOptions = { description: 'Connect to a running Chrome instance using port forwarding. For more details see: https://developer.chrome.com/docs/devtools/remote-debugging/local-server.', alias: 'u', - coerce: (url: string) => { - try { - new URL(url); - } catch { - throw new Error(`Provided browserUrl ${url} is not valid URL.`); - } - return url; - }, - }, +coerce: (url: string) => { + if (!url.trim()) return undefined; + + try { + const parsed = new URL(url); + return parsed.href.replace(/\/$/, ''); // remove trailing slash + } catch { + throw new Error(`Provided browserUrl ${url} is not valid URL.`); + } +}, + headless: { type: 'boolean', description: 'Whether to run in headless (no UI) mode.', diff --git a/src/tools/console.ts b/src/tools/console.ts index 9a3ff114..06d1e2e9 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -17,5 +17,6 @@ export const consoleTool = defineTool({ schema: {}, handler: async (_request, response) => { response.setIncludeConsoleData(true); + return (response as unknown).cleanedConsoleData ?? []; }, });