Skip to content
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ The Chrome DevTools MCP server supports the following configuration option:
- **`--channel`**
Specify a different Chrome channel that should be used. The default is the stable channel version.
- **Type:** string
- **Choices:** `stable`, `canary`, `beta`, `dev`
- **Choices:** `canary`, `dev`, `beta`, `stable`

- **`--logFile`/ `--log-file`**
Path to a file to write debug logs to. Set the env variable `DEBUG` to `*` to enable verbose logs. Useful for submitting bug reports.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"typecheck": "tsc --noEmit",
"format": "eslint --cache --fix . && prettier --write --cache .",
"check-format": "eslint --cache . && prettier --check --cache .;",
"gen": "npm run build && npm run docs:generate && npm run cli:generate && npm run update-tool-call-metrics && npm run format",
"gen": "npm run build && npm run docs:generate && npm run cli:generate && npm run update-tool-call-metrics && npm run update-flag-usage-metrics && npm run format",
"docs:generate": "node --experimental-strip-types scripts/generate-docs.ts",
"start": "npm run build && node build/src/index.js",
"start-debug": "DEBUG=mcp:* DEBUG_COLORS=false npm run build && node build/src/index.js",
Expand All @@ -28,6 +28,7 @@
"verify-server-json-version": "node --experimental-strip-types scripts/verify-server-json-version.ts",
"update-lighthouse": "node --experimental-strip-types scripts/update-lighthouse.ts",
"update-tool-call-metrics": "node --experimental-strip-types scripts/update_tool_call_metrics.ts",
"update-flag-usage-metrics": "node --experimental-strip-types scripts/update_flag_usage_metrics.ts",
"verify-npm-package": "node scripts/verify-npm-package.mjs",
"eval": "npm run build && node --experimental-strip-types scripts/eval_gemini.ts",
"count-tokens": "node --experimental-strip-types scripts/count_tokens.ts"
Expand Down
51 changes: 51 additions & 0 deletions scripts/update_flag_usage_metrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as fs from 'node:fs';
import * as path from 'node:path';

import {cliOptions} from '../build/src/bin/chrome-devtools-mcp-cli-options.js';
import {
getPossibleFlagMetrics,
type FlagMetric,
} from '../build/src/telemetry/flagUtils.js';
import {applyToExisting} from '../build/src/telemetry/toolMetricsUtils.js';

function writeFlagUsageMetrics() {
const outputPath = path.resolve('src/telemetry/flag_usage_metrics.json');

const dir = path.dirname(outputPath);
if (!fs.existsSync(dir)) {
throw new Error(`Error: Directory ${dir} does not exist.`);
}

let existingMetrics: FlagMetric[] = [];
if (fs.existsSync(outputPath)) {
try {
existingMetrics = JSON.parse(
fs.readFileSync(outputPath, 'utf8'),
) as FlagMetric[];
} catch {
console.warn(
`Warning: Failed to parse existing metrics from ${outputPath}. Starting fresh.`,
);
}
}

const newMetrics = getPossibleFlagMetrics(cliOptions);
const mergedMetrics = applyToExisting<FlagMetric>(
existingMetrics,
newMetrics,
);

fs.writeFileSync(outputPath, JSON.stringify(mergedMetrics, null, 2) + '\n');

console.log(
`Successfully wrote ${mergedMetrics.length} flag usage metrics to ${outputPath}`,
);
}

writeFlagUsageMetrics();
2 changes: 1 addition & 1 deletion src/bin/chrome-devtools-mcp-cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const cliOptions = {
type: 'string',
description:
'Specify a different Chrome channel that should be used. The default is the stable channel version.',
choices: ['stable', 'canary', 'beta', 'dev'] as const,
choices: ['canary', 'dev', 'beta', 'stable'] as const,
conflicts: ['browserUrl', 'wsEndpoint', 'executablePath'],
},
logFile: {
Expand Down
65 changes: 61 additions & 4 deletions src/telemetry/flagUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import type {FlagUsage} from './types.js';

type CliOptions = typeof cliOptions;

/**
* For enums, log the value as uppercase.
* We're going to have an enum for such flags with choices represented
* as an `enum` where the keys of the enum will map to the uppercase `choice`.
*/
function formatEnumChoice(snakeCaseName: string, choice: string): string {
return `${snakeCaseName}_${choice}`.toUpperCase();
}

/**
* Computes telemetry flag usage from parsed arguments and CLI options.
*
Expand All @@ -21,6 +30,8 @@ type CliOptions = typeof cliOptions;
* - The provided value differs from the default value.
* - Boolean flags are logged with their literal value.
* - String flags with defined `choices` (Enums) are logged as their uppercase value.
*
* IMPORTANT: keep getPossibleFlagMetrics() in sync with this function.
*/
export function computeFlagUsage(
args: Record<string, unknown>,
Expand Down Expand Up @@ -49,12 +60,58 @@ export function computeFlagUsage(
'choices' in config &&
config.choices
) {
// For enums, log the value as uppercase
// We're going to have an enum for such flags with choices represented
// as an `enum` where the keys of the enum will map to the uppercase `choice`.
usage[snakeCaseName] = `${snakeCaseName}_${value}`.toUpperCase();
usage[snakeCaseName] = formatEnumChoice(snakeCaseName, value);
}
}

return usage;
}

export interface FlagMetric {
name: string;
flagType: 'boolean' | 'enum';
choices?: string[];
}

/**
* Computes the list of possible flag metrics based on the CLI options.
*
* IMPORTANT: keep this function in sync with computeFlagUsage().
*/
export function getPossibleFlagMetrics(options: CliOptions): FlagMetric[] {
const metrics: FlagMetric[] = [];

for (const [flagName, config] of Object.entries(options)) {
const snakeCaseName = toSnakeCase(flagName);

// _present is always a possible metric
metrics.push({
name: `${snakeCaseName}_present`,
flagType: 'boolean',
});

if (config.type === 'boolean') {
metrics.push({
name: snakeCaseName,
flagType: 'boolean',
});
} else if (
config.type === 'string' &&
'choices' in config &&
config.choices
) {
metrics.push({
name: snakeCaseName,
flagType: 'enum',
choices: [
`${snakeCaseName.toUpperCase()}_UNSPECIFIED`,
...config.choices.map(choice =>
formatEnumChoice(snakeCaseName, choice),
),
],
});
}
}

return metrics;
}
Loading