fix(cli): send gemini-cli version as mcp client version (#13407)

Co-authored-by: Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
David Soria Parra
2026-01-20 22:01:18 +00:00
committed by GitHub
parent 645e2ec041
commit b288f124b2
7 changed files with 72 additions and 16 deletions

View File

@@ -451,6 +451,7 @@ export async function loadCliConfig(
workspaceDir: cwd,
enabledExtensionOverrides: argv.extensions,
eventEmitter: appEvents as EventEmitter<ExtensionEvents>,
clientVersion: await getVersion(),
});
await extensionManager.loadExtensions();
@@ -653,6 +654,7 @@ export async function loadCliConfig(
return new Config({
sessionId,
clientVersion: await getVersion(),
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
sandbox: sandboxConfig,
targetDir: cwd,

View File

@@ -76,6 +76,7 @@ interface ExtensionManagerParams {
requestSetting: ((setting: ExtensionSetting) => Promise<string>) | null;
workspaceDir: string;
eventEmitter?: EventEmitter<ExtensionEvents>;
clientVersion?: string;
}
/**
@@ -105,6 +106,7 @@ export class ExtensionManager extends ExtensionLoader {
telemetry: options.settings.telemetry,
interactive: false,
sessionId: randomUUID(),
clientVersion: options.clientVersion ?? 'unknown',
targetDir: options.workspaceDir,
cwd: options.workspaceDir,
model: '',

View File

@@ -290,6 +290,7 @@ export interface SandboxConfig {
export interface ConfigParameters {
sessionId: string;
clientVersion?: string;
embeddingModel?: string;
sandbox?: SandboxConfig;
targetDir: string;
@@ -415,6 +416,7 @@ export class Config {
private agentRegistry!: AgentRegistry;
private skillManager!: SkillManager;
private sessionId: string;
private clientVersion: string;
private fileSystemService: FileSystemService;
private contentGeneratorConfig!: ContentGeneratorConfig;
private contentGenerator!: ContentGenerator;
@@ -553,6 +555,7 @@ export class Config {
constructor(params: ConfigParameters) {
this.sessionId = params.sessionId;
this.clientVersion = params.clientVersion ?? 'unknown';
this.embeddingModel =
params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
this.fileSystemService = new StandardFileSystemService();
@@ -810,6 +813,7 @@ export class Config {
this.toolRegistry = await this.createToolRegistry();
discoverToolsHandle?.end();
this.mcpClientManager = new McpClientManager(
this.clientVersion,
this.toolRegistry,
this,
this.eventEmitter,

View File

@@ -66,7 +66,7 @@ describe('McpClientManager', () => {
mockConfig.getMcpServers.mockReturnValue({
'test-server': {},
});
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(mockedMcpClient.connect).toHaveBeenCalledOnce();
expect(mockedMcpClient.discover).toHaveBeenCalledOnce();
@@ -79,7 +79,7 @@ describe('McpClientManager', () => {
'server-2': {},
'server-3': {},
});
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
// Each client should be connected/discovered
@@ -94,7 +94,7 @@ describe('McpClientManager', () => {
mockConfig.getMcpServers.mockReturnValue({
'test-server': {},
});
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
expect(manager.getDiscoveryState()).toBe(MCPDiscoveryState.NOT_STARTED);
const promise = manager.startConfiguredMcpServers();
expect(manager.getDiscoveryState()).toBe(MCPDiscoveryState.IN_PROGRESS);
@@ -107,7 +107,7 @@ describe('McpClientManager', () => {
'test-server': {},
});
mockConfig.isTrustedFolder.mockReturnValue(false);
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(mockedMcpClient.connect).not.toHaveBeenCalled();
expect(mockedMcpClient.discover).not.toHaveBeenCalled();
@@ -118,7 +118,7 @@ describe('McpClientManager', () => {
'test-server': {},
});
mockConfig.getBlockedMcpServers.mockReturnValue(['test-server']);
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(mockedMcpClient.connect).not.toHaveBeenCalled();
expect(mockedMcpClient.discover).not.toHaveBeenCalled();
@@ -130,14 +130,14 @@ describe('McpClientManager', () => {
'another-server': {},
});
mockConfig.getAllowedMcpServers.mockReturnValue(['another-server']);
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(mockedMcpClient.connect).toHaveBeenCalledOnce();
expect(mockedMcpClient.discover).toHaveBeenCalledOnce();
});
it('should start servers from extensions', async () => {
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startExtension({
name: 'test-extension',
mcpServers: {
@@ -154,7 +154,7 @@ describe('McpClientManager', () => {
});
it('should not start servers from disabled extensions', async () => {
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startExtension({
name: 'test-extension',
mcpServers: {
@@ -175,7 +175,7 @@ describe('McpClientManager', () => {
'test-server': {},
});
mockConfig.getBlockedMcpServers.mockReturnValue(['test-server']);
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(manager.getBlockedMcpServers()).toEqual([
{ name: 'test-server', extensionName: '' },
@@ -188,7 +188,7 @@ describe('McpClientManager', () => {
'test-server': {},
});
mockedMcpClient.getServerConfig.mockReturnValue({});
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(mockedMcpClient.connect).toHaveBeenCalledTimes(1);
@@ -207,7 +207,7 @@ describe('McpClientManager', () => {
'test-server': {},
});
mockedMcpClient.getServerConfig.mockReturnValue({});
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
expect(mockedMcpClient.connect).toHaveBeenCalledTimes(1);
@@ -221,7 +221,7 @@ describe('McpClientManager', () => {
});
it('should throw an error if the server does not exist', async () => {
const manager = new McpClientManager(toolRegistry, mockConfig);
const manager = new McpClientManager('0.0.1', toolRegistry, mockConfig);
await expect(manager.restartServer('non-existent')).rejects.toThrow(
'No MCP server registered with the name "non-existent"',
);
@@ -247,7 +247,11 @@ describe('McpClientManager', () => {
}) as unknown as McpClient,
);
const manager = new McpClientManager({} as ToolRegistry, mockConfig);
const manager = new McpClientManager(
'0.0.1',
{} as ToolRegistry,
mockConfig,
);
mockConfig.getMcpServers.mockReturnValue({
'server-with-instructions': {},
@@ -282,7 +286,11 @@ describe('McpClientManager', () => {
'test-server': {},
});
const manager = new McpClientManager({} as ToolRegistry, mockConfig);
const manager = new McpClientManager(
'0.0.1',
{} as ToolRegistry,
mockConfig,
);
await expect(manager.startConfiguredMcpServers()).resolves.not.toThrow();
});
@@ -301,7 +309,11 @@ describe('McpClientManager', () => {
'test-server': {},
});
const manager = new McpClientManager({} as ToolRegistry, mockConfig);
const manager = new McpClientManager(
'0.0.1',
{} as ToolRegistry,
mockConfig,
);
await manager.startConfiguredMcpServers();
await expect(manager.restartServer('test-server')).resolves.not.toThrow();

View File

@@ -27,6 +27,7 @@ import { debugLogger } from '../utils/debugLogger.js';
*/
export class McpClientManager {
private clients: Map<string, McpClient> = new Map();
private readonly clientVersion: string;
private readonly toolRegistry: ToolRegistry;
private readonly cliConfig: Config;
// If we have ongoing MCP client discovery, this completes once that is done.
@@ -40,10 +41,12 @@ export class McpClientManager {
}> = [];
constructor(
clientVersion: string,
toolRegistry: ToolRegistry,
cliConfig: Config,
eventEmitter?: EventEmitter,
) {
this.clientVersion = clientVersion;
this.toolRegistry = toolRegistry;
this.cliConfig = cliConfig;
this.eventEmitter = eventEmitter;
@@ -183,6 +186,7 @@ export class McpClientManager {
this.cliConfig.getWorkspaceContext(),
this.cliConfig,
this.cliConfig.getDebugMode(),
this.clientVersion,
async () => {
debugLogger.log('Tools changed, updating Gemini context...');
await this.scheduleMcpContextRefresh();

View File

@@ -133,6 +133,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -213,6 +214,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -264,6 +266,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await expect(client.discover({} as Config)).rejects.toThrow(
@@ -319,6 +322,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await expect(client.discover({} as Config)).rejects.toThrow(
@@ -378,6 +382,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -451,6 +456,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -527,6 +533,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -610,6 +617,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -690,6 +698,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
await client.discover({} as Config);
@@ -739,6 +748,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
@@ -775,6 +785,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
@@ -830,6 +841,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
onToolsUpdatedSpy,
);
@@ -900,6 +912,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
@@ -970,6 +983,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
onToolsUpdatedSpy,
);
@@ -982,6 +996,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
onToolsUpdatedSpy,
);
@@ -1064,6 +1079,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
);
await client.connect();
@@ -1128,6 +1144,7 @@ describe('mcp-client', () => {
workspaceContext,
{ sanitizationConfig: EMPTY_CONFIG } as Config,
false,
'0.0.1',
onToolsUpdatedSpy,
);
@@ -1675,6 +1692,7 @@ describe('connectToMcpServer with OAuth', () => {
);
const client = await connectToMcpServer(
'0.0.1',
'test-server',
{ httpUrl: serverUrl, oauth: { enabled: true } },
false,
@@ -1720,6 +1738,7 @@ describe('connectToMcpServer with OAuth', () => {
);
const client = await connectToMcpServer(
'0.0.1',
'test-server',
{ httpUrl: serverUrl, oauth: { enabled: true } },
false,
@@ -1775,6 +1794,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
await expect(
connectToMcpServer(
'0.0.1',
'test-server',
{ url: 'http://test-server', type: 'http' },
false,
@@ -1794,6 +1814,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
await expect(
connectToMcpServer(
'0.0.1',
'test-server',
{ url: 'http://test-server', type: 'sse' },
false,
@@ -1812,6 +1833,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
.mockResolvedValueOnce(undefined);
const client = await connectToMcpServer(
'0.0.1',
'test-server',
{ url: 'http://test-server' },
false,
@@ -1834,6 +1856,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
await expect(
connectToMcpServer(
'0.0.1',
'test-server',
{ url: 'http://test-server' },
false,
@@ -1851,6 +1874,7 @@ describe('connectToMcpServer - HTTP→SSE fallback', () => {
.mockResolvedValueOnce(undefined);
const client = await connectToMcpServer(
'0.0.1',
'test-server',
{ url: 'http://test-server' },
false,
@@ -1921,6 +1945,7 @@ describe('connectToMcpServer - OAuth with transport fallback', () => {
.mockResolvedValueOnce(undefined);
const client = await connectToMcpServer(
'0.0.1',
'test-server',
{ url: 'http://test-server', oauth: { enabled: true } },
false,

View File

@@ -122,6 +122,7 @@ export class McpClient {
private readonly workspaceContext: WorkspaceContext,
private readonly cliConfig: Config,
private readonly debugMode: boolean,
private readonly clientVersion: string,
private readonly onToolsUpdated?: (signal?: AbortSignal) => Promise<void>,
) {}
@@ -137,6 +138,7 @@ export class McpClient {
this.updateStatus(MCPServerStatus.CONNECTING);
try {
this.client = await connectToMcpServer(
this.clientVersion,
this.serverName,
this.serverConfig,
this.debugMode,
@@ -715,6 +717,7 @@ async function createTransportWithOAuth(
*/
export async function discoverMcpTools(
clientVersion: string,
mcpServers: Record<string, MCPServerConfig>,
mcpServerCommand: string | undefined,
toolRegistry: ToolRegistry,
@@ -730,6 +733,7 @@ export async function discoverMcpTools(
const discoveryPromises = Object.entries(mcpServers).map(
([mcpServerName, mcpServerConfig]) =>
connectAndDiscover(
clientVersion,
mcpServerName,
mcpServerConfig,
toolRegistry,
@@ -808,6 +812,7 @@ export function populateMcpServerCommand(
* @returns Promise that resolves when discovery is complete
*/
export async function connectAndDiscover(
clientVersion: string,
mcpServerName: string,
mcpServerConfig: MCPServerConfig,
toolRegistry: ToolRegistry,
@@ -821,6 +826,7 @@ export async function connectAndDiscover(
let mcpClient: Client | undefined;
try {
mcpClient = await connectToMcpServer(
clientVersion,
mcpServerName,
mcpServerConfig,
debugMode,
@@ -1331,6 +1337,7 @@ async function retryWithOAuth(
* @throws An error if the connection fails or the configuration is invalid.
*/
export async function connectToMcpServer(
clientVersion: string,
mcpServerName: string,
mcpServerConfig: MCPServerConfig,
debugMode: boolean,
@@ -1340,7 +1347,7 @@ export async function connectToMcpServer(
const mcpClient = new Client(
{
name: 'gemini-cli-mcp-client',
version: '0.0.1',
version: clientVersion,
},
{
// Use a tolerant validator so bad output schemas don't block discovery.