Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
73 changes: 62 additions & 11 deletions src/McpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string>();
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 {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++;
}
Expand All @@ -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');
Expand All @@ -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 {
Expand Down
17 changes: 10 additions & 7 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,17 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
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,
Expand Down
20 changes: 11 additions & 9 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
1 change: 1 addition & 0 deletions src/tools/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export const consoleTool = defineTool({
schema: {},
handler: async (_request, response) => {
response.setIncludeConsoleData(true);
return (response as unknown).cleanedConsoleData ?? [];
},
});