diff --git a/src/index.ts b/src/index.ts index 7dabf6766..2689e34a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -57,6 +57,13 @@ export async function createMcpServer( return {}; }); + server.server.oninitialized = () => { + const clientName = server.server.getClientVersion()?.name; + if (clientName) { + clearcutLogger?.setClientName(clientName); + } + }; + let context: McpContext; async function getContext(): Promise { const chromeArgs: string[] = (serverArgs.chromeArg ?? []).map(String); diff --git a/src/telemetry/ClearcutLogger.ts b/src/telemetry/ClearcutLogger.ts index 7deadbf01..dfb592451 100644 --- a/src/telemetry/ClearcutLogger.ts +++ b/src/telemetry/ClearcutLogger.ts @@ -10,7 +10,12 @@ import {logger} from '../logger.js'; import type {LocalState, Persistence} from './persistence.js'; import {FilePersistence} from './persistence.js'; -import {type FlagUsage, WatchdogMessageType, OsType} from './types.js'; +import { + McpClient, + type FlagUsage, + WatchdogMessageType, + OsType, +} from './types.js'; import {WatchdogClient} from './WatchdogClient.js'; const MS_PER_DAY = 24 * 60 * 60 * 1000; @@ -31,6 +36,7 @@ function detectOsType(): OsType { export class ClearcutLogger { #persistence: Persistence; #watchdog: WatchdogClient; + #mcpClient: McpClient; constructor(options: { appVersion: string; @@ -53,6 +59,18 @@ export class ClearcutLogger { clearcutForceFlushIntervalMs: options.clearcutForceFlushIntervalMs, clearcutIncludePidHeader: options.clearcutIncludePidHeader, }); + this.#mcpClient = McpClient.MCP_CLIENT_UNSPECIFIED; + } + + setClientName(clientName: string): void { + const lowerName = clientName.toLowerCase(); + if (lowerName.includes('claude')) { + this.#mcpClient = McpClient.MCP_CLIENT_CLAUDE_CODE; + } else if (lowerName.includes('gemini')) { + this.#mcpClient = McpClient.MCP_CLIENT_GEMINI_CLI; + } else { + this.#mcpClient = McpClient.MCP_CLIENT_OTHER; + } } async logToolInvocation(args: { @@ -63,6 +81,7 @@ export class ClearcutLogger { this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { + mcp_client: this.#mcpClient, tool_invocation: { tool_name: args.toolName, success: args.success, @@ -76,6 +95,7 @@ export class ClearcutLogger { this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { + mcp_client: this.#mcpClient, server_start: { flag_usage: flagUsage, }, @@ -99,6 +119,7 @@ export class ClearcutLogger { this.#watchdog.send({ type: WatchdogMessageType.LOG_EVENT, payload: { + mcp_client: this.#mcpClient, daily_active: { days_since_last_active: daysSince, }, diff --git a/src/telemetry/types.ts b/src/telemetry/types.ts index 24d18b94f..8d8325419 100644 --- a/src/telemetry/types.ts +++ b/src/telemetry/types.ts @@ -75,6 +75,7 @@ export enum McpClient { MCP_CLIENT_UNSPECIFIED = 0, MCP_CLIENT_CLAUDE_CODE = 1, MCP_CLIENT_GEMINI_CLI = 2, + MCP_CLIENT_OTHER = 3, } // IPC types for messages between the main process and the diff --git a/tests/telemetry/ClearcutLogger.test.ts b/tests/telemetry/ClearcutLogger.test.ts index b8b1d1dc7..960689613 100644 --- a/tests/telemetry/ClearcutLogger.test.ts +++ b/tests/telemetry/ClearcutLogger.test.ts @@ -54,6 +54,24 @@ describe('ClearcutLogger', () => { }); }); + describe('setClientName', () => { + it('appends mapped mcp_client to payload', async () => { + const logger = new ClearcutLogger({ + persistence: mockPersistence, + appVersion: '1.0.0', + watchdogClient: mockWatchdogClient, + }); + + logger.setClientName('gemini-cli-mcp-client'); + await logger.logServerStart({headless: true}); + + assert(mockWatchdogClient.send.calledOnce); + const msg = mockWatchdogClient.send.firstCall.args[0]; + assert.strictEqual(msg.type, WatchdogMessageType.LOG_EVENT); + assert.strictEqual(msg.payload.mcp_client, 2); // 2 is MCP_CLIENT_GEMINI_CLI + }); + }); + describe('logServerStart', () => { it('logs flag usage', async () => { const logger = new ClearcutLogger({