Skip to content
Merged
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
20 changes: 10 additions & 10 deletions src/McpContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
type Browser,
type BrowserContext,
type ConsoleMessage,
type Debugger,
type HTTPRequest,
type Page,
type ScreenRecorder,
Expand All @@ -41,6 +40,7 @@ import {listPages} from './tools/pages.js';
import {CLOSE_PAGE_ERROR} from './tools/ToolDefinition.js';
import type {Context, SupportedExtensions} from './tools/ToolDefinition.js';
import type {TraceResult} from './trace-processing/parse.js';
import type {Logger} from './types.js';
import type {
EmulationSettings,
GeolocationOptions,
Expand All @@ -67,7 +67,7 @@ const NAVIGATION_TIMEOUT = 10_000;

export class McpContext implements Context {
browser: Browser;
logger: Debugger;
logger: Logger;

// Maps LLM-provided isolatedContext name → Puppeteer BrowserContext.
#isolatedContexts = new Map<string, BrowserContext>();
Expand Down Expand Up @@ -102,7 +102,7 @@ export class McpContext implements Context {

private constructor(
browser: Browser,
logger: Debugger,
logger: Logger,
options: McpContextOptions,
locatorClass: typeof Locator,
) {
Expand Down Expand Up @@ -153,7 +153,7 @@ export class McpContext implements Context {

static async from(
browser: Browser,
logger: Debugger,
logger: Logger,
opts: McpContextOptions,
/* Let tests use unbundled Locator class to avoid overly strict checks within puppeteer that fail when mixing bundled and unbundled class instances */
locatorClass: typeof Locator = Locator,
Expand Down Expand Up @@ -236,15 +236,15 @@ export class McpContext implements Context {

resolveCdpRequestId(page: McpPage, cdpRequestId: string): number | undefined {
if (!cdpRequestId) {
this.logger('no network request');
this.logger?.('no network request');
return;
}
const request = this.#networkCollector.find(page.pptrPage, request => {
// @ts-expect-error id is internal.
return request.id === cdpRequestId;
});
if (!request) {
this.logger('no network request for ' + cdpRequestId);
this.logger?.('no network request for ' + cdpRequestId);
return;
}
return this.#networkCollector.getIdForResource(request);
Expand Down Expand Up @@ -591,7 +591,7 @@ export class McpContext implements Context {
this.#mcpPages.set(page, mcpPage);
// We emulate a focused page for all pages to support multi-agent workflows.
void page.emulateFocusedPage(true).catch(error => {
this.logger('Error turning on focused page emulation', error);
this.logger?.('Error turning on focused page emulation', error);
});
}
mcpPage.isolatedContextName = isolatedContextNames.get(page);
Expand Down Expand Up @@ -655,7 +655,7 @@ export class McpContext implements Context {
page = await target.asPage();
this.#extensionPages.set(target, page);
} catch (e) {
this.logger('Failed to get page for extension target', e);
this.logger?.('Failed to get page for extension target', e);
}
}
}
Expand Down Expand Up @@ -696,7 +696,7 @@ export class McpContext implements Context {
}

async detectOpenDevToolsWindows() {
this.logger('Detecting open DevTools windows');
this.logger?.('Detecting open DevTools windows');
const {pages} = await this.#getAllPages();

await Promise.all(
Expand Down Expand Up @@ -769,7 +769,7 @@ export class McpContext implements Context {
await fs.writeFile(filePath, data);
return {filename: filePath};
} catch (err) {
this.logger(err);
this.logger?.(err);
throw new Error('Could not save a file', {cause: err});
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/McpPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,22 +283,22 @@ export class McpPage implements ContextPage {
for (const handle of oldHandles) {
await handle
.dispose()
.catch(e => logger('Failed to dispose old handle', e));
.catch(e => logger?.('Failed to dispose old handle', e));
}
}

const cdpElementIds = await Promise.all(
elementHandles.map(async (elementHandle, index) => {
const backendNodeId = await elementHandle.backendNodeId();
if (!backendNodeId) {
logger(
logger?.(
`No backendNodeId for stashed DOM element with index ${index}`,
);
return `stashed-${index}`;
}
const cdpElementId = this.resolveCdpElementId(backendNodeId);
if (!cdpElementId) {
logger(
logger?.(
`Could not get cdpElementId for backend node ${backendNodeId}`,
);
return `stashed-${index}`;
Expand Down Expand Up @@ -371,12 +371,12 @@ export class McpPage implements ContextPage {

resolveCdpElementId(cdpBackendNodeId: number): string | undefined {
if (!cdpBackendNodeId) {
logger('no cdpBackendNodeId');
logger?.('no cdpBackendNodeId');
return;
}
const snapshot = this.textSnapshot;
if (!snapshot) {
logger('no text snapshot');
logger?.('no text snapshot');
return;
}
// TODO: index by backendNodeId instead.
Expand All @@ -395,10 +395,10 @@ export class McpPage implements ContextPage {

async getDevToolsData(): Promise<DevToolsData> {
try {
logger('Getting DevTools UI data');
logger?.('Getting DevTools UI data');
const devtoolsPage = this.devToolsPage;
if (!devtoolsPage) {
logger('No DevTools page detected');
logger?.('No DevTools page detected');
return {};
}
const {cdpRequestId, cdpBackendNodeId} = await devtoolsPage.evaluate(
Expand All @@ -421,7 +421,7 @@ export class McpPage implements ContextPage {
);
return {cdpBackendNodeId, cdpRequestId};
} catch (err) {
logger('error getting devtools data', err);
logger?.('error getting devtools data', err);
}
return {};
}
Expand Down
8 changes: 4 additions & 4 deletions src/PageCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class PageCollector<T> {
}
this.addPage(page);
} catch (err) {
logger('Error getting a page for a target onTargetCreated', err);
logger?.('Error getting a page for a target onTargetCreated', err);
}
};

Expand All @@ -104,7 +104,7 @@ export class PageCollector<T> {
}
this.cleanupPageDestroyed(page);
} catch (err) {
logger('Error getting a page for a target onTargetDestroyed', err);
logger?.('Error getting a page for a target onTargetDestroyed', err);
}
};

Expand Down Expand Up @@ -335,7 +335,7 @@ class PageEventSubscriber {
inspectorIssue,
)[0];
if (!issue) {
logger('No issue mapping for for the issue: ', inspectorIssue.code);
logger?.('No issue mapping for for the issue: ', inspectorIssue.code);
return;
}

Expand All @@ -353,7 +353,7 @@ class PageEventSubscriber {
},
);
} catch (error) {
logger('Error creating a new issue', error);
logger?.('Error creating a new issue', error);
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/TextSnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export class TextSnapshot {
collect(node);
}
} catch (e) {
logger(
logger?.(
`Failed to collect descendants for backend node ${backendNodeId}`,
e,
);
Expand Down
6 changes: 3 additions & 3 deletions src/ToolHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,11 @@ export class ToolHandler {
const startTime = Date.now();
let success = false;
try {
logger(
logger?.(
`${this.tool.name} request: ${JSON.stringify(params, null, ' ')}`,
);
const context = await this.getContext();
logger(`${this.tool.name} context: resolved`);
logger?.(`${this.tool.name} context: resolved`);
await context.detectOpenDevToolsWindows();
const response = this.serverArgs.slim
? new SlimMcpResponse(this.serverArgs)
Expand Down Expand Up @@ -277,7 +277,7 @@ export class ToolHandler {
}
return result;
} catch (err) {
logger(`${this.tool.name} error:`, err, err?.stack);
logger?.(`${this.tool.name} error:`, err, err?.stack);
let errorText = err && 'message' in err ? err.message : String(err);
if ('cause' in err && err.cause) {
errorText += `\nCause: ${err.cause.message}`;
Expand Down
4 changes: 2 additions & 2 deletions src/WaitForHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export class WaitForHelper {
}
return;
})
.catch(error => logger(error));
.catch(error => logger?.(error));

try {
await action();
Expand All @@ -183,7 +183,7 @@ export class WaitForHelper {
// the correct context
await this.waitForStableDom();
} catch (error) {
logger(error);
logger?.(error);
} finally {
this.#abortController.abort();
}
Expand Down
10 changes: 5 additions & 5 deletions src/bin/chrome-devtools-mcp-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const logFile = args.logFile ? saveLogsToFile(args.logFile) : undefined;

if (process.env['CHROME_DEVTOOLS_MCP_CRASH_ON_UNCAUGHT'] !== 'true') {
process.on('unhandledRejection', (reason, promise) => {
logger('Unhandled promise rejection', promise, reason);
logger?.('Unhandled promise rejection', promise, reason);
});
}

Expand All @@ -43,13 +43,13 @@ async function shutdown(reason: string): Promise<void> {
return;
}
shuttingDown = true;
logger(`Shutting down (${reason})`);
logger?.(`Shutting down (${reason})`);
// Backstop in case browser teardown hangs (e.g. unresponsive Chrome,
// slow beforeunload handlers, many tabs). Exits 0 because we still
// honored the shutdown request; the log line preserves observability.
// Unref'd so it doesn't keep the loop alive on the clean path.
setTimeout(() => {
logger('Shutdown timeout exceeded, forcing exit');
logger?.('Shutdown timeout exceeded, forcing exit');
process.exit(0);
}, 10000).unref();
await closeBrowser();
Expand All @@ -71,13 +71,13 @@ process.on('SIGHUP', () => {
void shutdown('SIGHUP');
});

logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
logger?.(`Starting Chrome DevTools MCP Server v${VERSION}`);
const {server} = await createMcpServer(args, {
logFile,
});
const transport = new StdioServerTransport();
await server.connect(transport);
logger('Chrome DevTools MCP Server connected');
logger?.('Chrome DevTools MCP Server connected');
logDisclaimers(args);
void ClearcutLogger.get()?.logDailyActiveIfNeeded();
void ClearcutLogger.get()?.logServerStart(computeFlagUsage(args, cliOptions));
8 changes: 4 additions & 4 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export async function ensureBrowserConnected(options: {
);
}

logger('Connecting Puppeteer to ', JSON.stringify(connectOptions));
logger?.('Connecting Puppeteer to ', JSON.stringify(connectOptions));
try {
// Assign mode before browser so a concurrent closeBrowser() never sees
// `browser` set with `browserMode` still undefined (would fall through
Expand All @@ -135,7 +135,7 @@ export async function ensureBrowserConnected(options: {
},
);
}
logger('Connected Puppeteer');
logger?.('Connected Puppeteer');
return browser;
}

Expand Down Expand Up @@ -296,12 +296,12 @@ export async function closeBrowser(): Promise<void> {
}
if (mode === 'launched') {
await b.close().catch(err => {
logger('Failed to close browser', err);
logger?.('Failed to close browser', err);
});
return;
}
await b.disconnect().catch(err => {
logger('Failed to disconnect from browser', err);
logger?.('Failed to disconnect from browser', err);
});
}

Expand Down
14 changes: 7 additions & 7 deletions src/daemon/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function waitForFile(filePath: string, removed = false) {

export async function startDaemon(mcpArgs: string[] = [], sessionId: string) {
if (isDaemonRunning(sessionId)) {
logger('Daemon is already running');
logger?.('Daemon is already running');
return;
}

Expand All @@ -79,7 +79,7 @@ export async function startDaemon(mcpArgs: string[] = [], sessionId: string) {
fs.unlinkSync(pidFilePath);
}

logger('Starting daemon...', ...mcpArgs);
logger?.('Starting daemon...', ...mcpArgs);
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH, ...mcpArgs], {
detached: true,
stdio: 'ignore',
Expand Down Expand Up @@ -116,27 +116,27 @@ export async function sendCommand(
const transport = new PipeTransport(socket, socket);
transport.onmessage = async (message: string) => {
clearTimeout(timer);
logger('onmessage', message);
logger?.('onmessage', message);
resolve(JSON.parse(message));
};
socket.on('error', error => {
clearTimeout(timer);
logger('Socket error:', error);
logger?.('Socket error:', error);
reject(error);
});
socket.on('close', () => {
clearTimeout(timer);
logger('Socket closed:');
logger?.('Socket closed:');
reject(new Error('Socket closed'));
});
logger('Sending message', command);
logger?.('Sending message', command);
transport.send(JSON.stringify(command));
});
}

export async function stopDaemon(sessionId: string) {
if (!isDaemonRunning(sessionId)) {
logger('Daemon is not running');
logger?.('Daemon is not running');
return;
}

Expand Down
Loading
Loading