feat(telemetry): recording a sanitized version of the client name (#2757)

This commit is contained in:
yulunz
2026-09-17 08:57:21 +00:00
committed by GitHub
parent 9ab482a7b1
commit 55fbc576f7
5 changed files with 88 additions and 17 deletions
+32 -15
View File
@@ -16,10 +16,12 @@ import {
bucketizeDaysSince,
bucketizeLatency,
buildContext,
sanitizeClientName,
sanitizeParams,
stripUnderscoreBeforeNumber,
} from './transformation.js';
import {
type ChromeDevToolsMcpExtension,
McpClient,
type FlagUsage,
WatchdogMessageType,
@@ -88,6 +90,7 @@ export class ClearcutLogger {
#persistence: Persistence;
#watchdog: WatchdogClient;
#mcpClient: McpClient;
#rawClientName?: string;
#state?: LocalState;
static initialize(options: ClearcutLoggerOptions): ClearcutLogger {
@@ -157,9 +160,28 @@ export class ClearcutLogger {
this.#mcpClient = McpClient.MCP_CLIENT_HERMES;
} else {
this.#mcpClient = McpClient.MCP_CLIENT_OTHER;
this.#rawClientName = sanitizeClientName(clientName);
}
}
#addMcpClient(
payload: ChromeDevToolsMcpExtension,
): ChromeDevToolsMcpExtension {
const ext: ChromeDevToolsMcpExtension = {
...payload,
mcp_client: this.#mcpClient,
};
if (
this.#mcpClient === McpClient.MCP_CLIENT_OTHER &&
this.#rawClientName !== undefined
) {
ext.raw_mcp_client_info = {
raw_client_name: this.#rawClientName,
};
}
return ext;
}
async logToolInvocation(args: {
toolName: string;
params: ShapeOutput<zod.ZodRawShape>;
@@ -194,22 +216,20 @@ export class ClearcutLogger {
this.#watchdog.send({
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.#mcpClient,
payload: this.#addMcpClient({
tool_invocation: tool_invocation,
},
}),
});
}
async logServerStart(flagUsage: FlagUsage): Promise<void> {
this.#watchdog.send({
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.#mcpClient,
payload: this.#addMcpClient({
server_start: {
flag_usage: flagUsage,
},
},
}),
});
}
@@ -222,12 +242,11 @@ export class ClearcutLogger {
this.#watchdog.send({
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.#mcpClient,
payload: this.#addMcpClient({
daily_active: {
days_since_last_active: bucketizeDaysSince(daysSince),
},
},
}),
});
this.#state.lastActive = new Date().toISOString();
@@ -244,15 +263,14 @@ export class ClearcutLogger {
}): Promise<void> {
this.#watchdog.send({
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.#mcpClient,
payload: this.#addMcpClient({
server_error: {
tool_name: args.toolName
? stripUnderscoreBeforeNumber(args.toolName)
: '',
error_code: args.errorCode,
},
},
}),
});
}
@@ -282,12 +300,11 @@ export class ClearcutLogger {
this.#watchdog.send({
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: this.#mcpClient,
payload: this.#addMcpClient({
tool_active: {
days_since_last_tool_call: bucketizeDaysSince(daysSinceToolCall),
},
},
}),
});
this.#state.lastToolCall = now.toISOString();
+15
View File
@@ -27,6 +27,21 @@ export function bucketizeDaysSince(days: number): number {
return Math.min(days, MAX_ACTIVE_DAYS);
}
export const REDACTED_CLIENT_NAME = '<redacted>';
const VALID_CLIENT_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
const MAX_CLIENT_NAME_LENGTH = 32;
export function sanitizeClientName(clientName: string): string {
if (
clientName.length > 0 &&
clientName.length < MAX_CLIENT_NAME_LENGTH &&
VALID_CLIENT_NAME_REGEX.test(clientName)
) {
return clientName;
}
return REDACTED_CLIENT_NAME;
}
export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']);
const SUPPORTED_ZOD_TYPES = [
+5
View File
@@ -10,6 +10,7 @@ import type {ErrorCode} from './errors.js';
export interface ChromeDevToolsMcpExtension {
os_type?: OsType;
mcp_client?: McpClient;
raw_mcp_client_info?: RawMcpClientInfo;
app_version?: string;
session_id?: string;
tool_invocation?: ToolInvocation;
@@ -20,6 +21,10 @@ export interface ChromeDevToolsMcpExtension {
tool_active?: ToolActive;
}
export interface RawMcpClientInfo {
raw_client_name: string;
}
export interface ServerError {
tool_name?: string;
error_code: ErrorCode;
+6 -2
View File
@@ -137,11 +137,12 @@ describe('ClearcutLogger', () => {
{name: 'xai-sdk', expected: 8}, // MCP_CLIENT_GROK
{name: 'github-copilot-developer', expected: 11}, // MCP_CLIENT_GITHUB_COPILOT
{name: 'copilot-intellij', expected: 11}, // MCP_CLIENT_GITHUB_COPILOT
{name: 'unknown-client', expected: 3}, // MCP_CLIENT_OTHER
{name: 'unknown-client', expected: 3, rawName: 'unknown-client'}, // MCP_CLIENT_OTHER
{name: 'unknown client', expected: 3, rawName: '<redacted>'}, // MCP_CLIENT_OTHER
{name: 'hermes-agent/1.0.0', expected: 12}, // MCP_CLIENT_HERMES
];
for (const {name, expected} of clients) {
for (const {name, expected, rawName} of clients) {
it(`maps ${name} client correctly`, async () => {
const logger = ClearcutLogger.initialize({
persistence: mockPersistence,
@@ -156,6 +157,9 @@ describe('ClearcutLogger', () => {
type: WatchdogMessageType.LOG_EVENT,
payload: {
mcp_client: expected,
...(rawName
? {raw_mcp_client_info: {raw_client_name: rawName}}
: {}),
server_start: {
flag_usage: {headless: true},
},
+30
View File
@@ -13,6 +13,7 @@ import {
buildContext,
getEnumValues,
MAX_ACTIVE_DAYS,
sanitizeClientName,
sanitizeParams,
stripUnderscoreBeforeNumber,
transformArgName,
@@ -277,3 +278,32 @@ describe('buildContext', () => {
);
});
});
describe('sanitizeClientName', () => {
it('returns valid alphanumeric client names with hyphens and underscores as is', () => {
const validNames = [
'a',
'my-custom_client123',
'Client_Name-1',
'a'.repeat(31),
];
for (const name of validNames) {
assert.strictEqual(sanitizeClientName(name), name);
}
});
it('redacts invalid or overly long client names', () => {
const invalidNames = [
'',
'a'.repeat(32),
'a'.repeat(100),
'client with spaces',
'client/1.0',
'client.name',
'client@home',
];
for (const name of invalidNames) {
assert.strictEqual(sanitizeClientName(name), '<redacted>');
}
});
});