豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "MCP server for Chrome DevTools",
"type": "module",
"bin": "./build/src/index.js",
"main": "index.js",
"main": "./build/src/server.js",
"scripts": {
"clean": "node -e \"require('fs').rmSync('build', {recursive: true, force: true})\"",
"bundle": "npm run clean && npm run build && rollup -c rollup.config.mjs && node -e \"require('fs').rmSync('build/node_modules', {recursive: true, force: true})\"",
Expand Down
213 changes: 5 additions & 208 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,11 @@ import './polyfill.js';

import process from 'node:process';

import type {Channel} from './browser.js';
import {ensureBrowserConnected, ensureBrowserLaunched} from './browser.js';
import {cliOptions, parseArguments} from './cli.js';
import {loadIssueDescriptions} from './issue-descriptions.js';
import {logger, saveLogsToFile} from './logger.js';
import {McpContext} from './McpContext.js';
import {McpResponse} from './McpResponse.js';
import {Mutex} from './Mutex.js';
import {SlimMcpResponse} from './SlimMcpResponse.js';
import {ClearcutLogger} from './telemetry/ClearcutLogger.js';
import {createMcpServer} from './server.js';
import {computeFlagUsage} from './telemetry/flagUtils.js';
import {bucketizeLatency} from './telemetry/metricUtils.js';
import {
McpServer,
StdioServerTransport,
type CallToolResult,
SetLevelRequestSchema,
} from './third_party/index.js';
import {ToolCategory} from './tools/categories.js';
import type {ToolDefinition} from './tools/ToolDefinition.js';
import {createTools} from './tools/tools.js';
import {StdioServerTransport} from './third_party/index.js';
import {VERSION} from './version.js';

export const args = parseArguments(VERSION);
Expand All @@ -44,81 +28,13 @@ if (
args.usageStatistics = false;
}

let clearcutLogger: ClearcutLogger | undefined;
if (args.usageStatistics) {
clearcutLogger = new ClearcutLogger({
logFile: args.logFile,
appVersion: VERSION,
clearcutEndpoint: args.clearcutEndpoint,
clearcutForceFlushIntervalMs: args.clearcutForceFlushIntervalMs,
clearcutIncludePidHeader: args.clearcutIncludePidHeader,
});
}

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

logger(`Starting Chrome DevTools MCP Server v${VERSION}`);
const server = new McpServer(
{
name: 'chrome_devtools',
title: 'Chrome DevTools MCP server',
version: VERSION,
},
{capabilities: {logging: {}}},
);
server.server.setRequestHandler(SetLevelRequestSchema, () => {
return {};
});

let context: McpContext;
async function getContext(): Promise<McpContext> {
const chromeArgs: string[] = (args.chromeArg ?? []).map(String);
const ignoreDefaultChromeArgs: string[] = (
args.ignoreDefaultChromeArg ?? []
).map(String);
if (args.proxyServer) {
chromeArgs.push(`--proxy-server=${args.proxyServer}`);
}
const devtools = args.experimentalDevtools ?? false;
const browser =
args.browserUrl || args.wsEndpoint || args.autoConnect
? await ensureBrowserConnected({
browserURL: args.browserUrl,
wsEndpoint: args.wsEndpoint,
wsHeaders: args.wsHeaders,
// Important: only pass channel, if autoConnect is true.
channel: args.autoConnect ? (args.channel as Channel) : undefined,
userDataDir: args.userDataDir,
devtools,
})
: await ensureBrowserLaunched({
headless: args.headless,
executablePath: args.executablePath,
channel: args.channel as Channel,
isolated: args.isolated ?? false,
userDataDir: args.userDataDir,
logFile,
viewport: args.viewport,
chromeArgs,
ignoreDefaultChromeArgs,
acceptInsecureCerts: args.acceptInsecureCerts,
devtools,
enableExtensions: args.categoryExtensions,
});

if (context?.browser !== browser) {
context = await McpContext.from(browser, logger, {
experimentalDevToolsDebugging: devtools,
experimentalIncludeAllPages: args.experimentalIncludeAllPages,
performanceCrux: args.performanceCrux,
});
}
return context;
}

const logDisclaimers = () => {
console.error(
Expand All @@ -142,128 +58,9 @@ For more details, visit: https://github.com/ChromeDevTools/chrome-devtools-mcp#u
}
};

const toolMutex = new Mutex();

function registerTool(tool: ToolDefinition): void {
if (
tool.annotations.category === ToolCategory.EMULATION &&
args.categoryEmulation === false
) {
return;
}
if (
tool.annotations.category === ToolCategory.PERFORMANCE &&
args.categoryPerformance === false
) {
return;
}
if (
tool.annotations.category === ToolCategory.NETWORK &&
args.categoryNetwork === false
) {
return;
}
if (
tool.annotations.category === ToolCategory.EXTENSIONS &&
args.categoryExtensions === false
) {
return;
}
if (
tool.annotations.conditions?.includes('computerVision') &&
!args.experimentalVision
) {
return;
}
if (
tool.annotations.conditions?.includes('experimentalInteropTools') &&
!args.experimentalInteropTools
) {
return;
}
if (
tool.annotations.conditions?.includes('screencast') &&
!args.experimentalScreencast
) {
return;
}
server.registerTool(
tool.name,
{
description: tool.description,
inputSchema: tool.schema,
annotations: tool.annotations,
},
async (params): Promise<CallToolResult> => {
const guard = await toolMutex.acquire();
const startTime = Date.now();
let success = false;
try {
logger(`${tool.name} request: ${JSON.stringify(params, null, ' ')}`);
const context = await getContext();
logger(`${tool.name} context: resolved`);
await context.detectOpenDevToolsWindows();
const response = args.slim
? new SlimMcpResponse(args)
: new McpResponse(args);

await tool.handler(
{
params,
},
response,
context,
);
const {content, structuredContent} = await response.handle(
tool.name,
context,
);
const result: CallToolResult & {
structuredContent?: Record<string, unknown>;
} = {
content,
};
success = true;
if (args.experimentalStructuredContent) {
result.structuredContent = structuredContent as Record<
string,
unknown
>;
}
return result;
} catch (err) {
logger(`${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}`;
}
return {
content: [
{
type: 'text',
text: errorText,
},
],
isError: true,
};
} finally {
void clearcutLogger?.logToolInvocation({
toolName: tool.name,
success,
latencyMs: bucketizeLatency(Date.now() - startTime),
});
guard.dispose();
}
},
);
}

const tools = createTools(args);
for (const tool of tools) {
registerTool(tool);
}

await loadIssueDescriptions();
const {server, clearcutLogger} = await createMcpServer(args, {
logFile,
});
const transport = new StdioServerTransport();
await server.connect(transport);
logger('Chrome DevTools MCP Server connected');
Expand Down
Loading