diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index c4d3caa3ac..dd91da0dd6 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -477,6 +477,10 @@ export class ClearcutLogger { gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_CONTENT_LENGTH, value: JSON.stringify(event.content_length), }, + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_MCP_SERVER_NAME, + value: JSON.stringify(event.mcp_server_name), + }, ]; if (event.metadata) { diff --git a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts index f700c259d5..5df2c240f4 100644 --- a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts +++ b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts @@ -50,6 +50,9 @@ export enum EventMetadataKey { // Logs whether the session was configured to respect gitignore files. GEMINI_CLI_START_SESSION_RESPECT_GITIGNORE = 12, + // Logs the output format of the session. + GEMINI_CLI_START_SESSION_OUTPUT_FORMAT = 94, + // ========================================================================== // User Prompt Event Keys // =========================================================================== @@ -64,6 +67,9 @@ export enum EventMetadataKey { // Logs the function name. GEMINI_CLI_TOOL_CALL_NAME = 14, + // Logs the MCP server name. + GEMINI_CLI_TOOL_CALL_MCP_SERVER_NAME = 95, + // Logs the user's decision about how to handle the tool call. GEMINI_CLI_TOOL_CALL_DECISION = 15, diff --git a/packages/core/src/telemetry/loggers.test.ts b/packages/core/src/telemetry/loggers.test.ts index 4086a448da..a2066441b8 100644 --- a/packages/core/src/telemetry/loggers.test.ts +++ b/packages/core/src/telemetry/loggers.test.ts @@ -76,7 +76,11 @@ import * as metrics from './metrics.js'; import { FileOperation } from './metrics.js'; import * as sdk from './sdk.js'; import { vi, describe, beforeEach, it, expect, afterEach } from 'vitest'; -import type { GenerateContentResponseUsageMetadata } from '@google/genai'; +import type { + CallableTool, + GenerateContentResponseUsageMetadata, +} from '@google/genai'; +import { DiscoveredMCPTool } from '../tools/mcp-tool.js'; import * as uiTelemetry from './uiTelemetry.js'; import { makeFakeConfig } from '../test-utils/config.js'; import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js'; @@ -930,6 +934,75 @@ describe('loggers', () => { 'event.timestamp': '2025-01-01T00:00:00.000Z', }); }); + + it('should log a tool call with mcp_server_name for MCP tools', () => { + const mockMcpTool = new DiscoveredMCPTool( + {} as CallableTool, + 'mock_mcp_server', + 'mock_mcp_tool', + 'tool description', + { + type: 'object', + properties: { + arg1: { type: 'string' }, + arg2: { type: 'number' }, + }, + required: ['arg1', 'arg2'], + }, + ); + + const call: CompletedToolCall = { + status: 'success', + request: { + name: 'mock_mcp_tool', + args: { arg1: 'value1', arg2: 2 }, + callId: 'test-call-id', + isClientInitiated: true, + prompt_id: 'prompt-id', + }, + response: { + callId: 'test-call-id', + responseParts: [{ text: 'test-response' }], + resultDisplay: undefined, + error: undefined, + errorType: undefined, + }, + tool: mockMcpTool, + invocation: {} as AnyToolInvocation, + durationMs: 100, + }; + const event = new ToolCallEvent(call); + + logToolCall(mockConfig, event); + + expect(mockLogger.emit).toHaveBeenCalledWith({ + body: 'Tool call: mock_mcp_tool. Success: true. Duration: 100ms.', + attributes: { + 'session.id': 'test-session-id', + 'user.email': 'test-user@example.com', + 'event.name': EVENT_TOOL_CALL, + 'event.timestamp': '2025-01-01T00:00:00.000Z', + function_name: 'mock_mcp_tool', + function_args: JSON.stringify( + { + arg1: 'value1', + arg2: 2, + }, + null, + 2, + ), + duration_ms: 100, + success: true, + prompt_id: 'prompt-id', + tool_type: 'mcp', + mcp_server_name: 'mock_mcp_server', + decision: undefined, + error: undefined, + error_type: undefined, + metadata: undefined, + }, + }); + }); }); describe('logMalformedJsonResponse', () => { diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts index dda0d8f7f8..6f7283dc1f 100644 --- a/packages/core/src/telemetry/types.ts +++ b/packages/core/src/telemetry/types.ts @@ -137,6 +137,7 @@ export class ToolCallEvent implements BaseTelemetryEvent { prompt_id: string; tool_type: 'native' | 'mcp'; content_length?: number; + mcp_server_name?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any metadata?: { [key: string]: any }; @@ -153,11 +154,16 @@ export class ToolCallEvent implements BaseTelemetryEvent { this.error = call.response.error?.message; this.error_type = call.response.errorType; this.prompt_id = call.request.prompt_id; - this.tool_type = - typeof call.tool !== 'undefined' && call.tool instanceof DiscoveredMCPTool - ? 'mcp' - : 'native'; this.content_length = call.response.contentLength; + if ( + typeof call.tool !== 'undefined' && + call.tool instanceof DiscoveredMCPTool + ) { + this.tool_type = 'mcp'; + this.mcp_server_name = call.tool.serverName; + } else { + this.tool_type = 'native'; + } if ( call.status === 'success' &&