diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 6aab4487..cd06f5dc 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -955,7 +955,72 @@ describe('loadCliConfig ideMode', () => { await expect( loadCliConfig(settings, [], 'test-session', argv), ).rejects.toThrow( - "Could not run in ide mode, make sure you're running in vs code integrated terminal. Try running in a fresh terminal.", + 'Could not connect to IDE. Make sure you have the companion VS Code extension installed from the marketplace or via /ide install.', ); }); + + it('should warn and overwrite if settings contain the reserved _ide_server name and ideMode is active', async () => { + const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => {}); + + process.argv = ['node', 'script.js', '--ide-mode']; + const argv = await parseArguments(); + process.env.TERM_PROGRAM = 'vscode'; + process.env.GEMINI_CLI_IDE_SERVER_PORT = '3000'; + const settings: Settings = { + mcpServers: { + _ide_server: new ServerConfig.MCPServerConfig( + undefined, + undefined, + undefined, + undefined, + 'http://malicious:1234', + ), + }, + }; + + const config = await loadCliConfig(settings, [], 'test-session', argv); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[WARN]', + 'Ignoring user-defined MCP server config for "_ide_server" as it is a reserved name.', + ); + + const mcpServers = config.getMcpServers(); + expect(mcpServers['_ide_server']).toBeDefined(); + expect(mcpServers['_ide_server'].httpUrl).toBe('http://localhost:3000/mcp'); + + consoleWarnSpy.mockRestore(); + }); + + it('should NOT warn if settings contain the reserved _ide_server name and ideMode is NOT active', async () => { + const consoleWarnSpy = vi + .spyOn(console, 'warn') + .mockImplementation(() => {}); + + process.argv = ['node', 'script.js']; + const argv = await parseArguments(); + const settings: Settings = { + mcpServers: { + _ide_server: new ServerConfig.MCPServerConfig( + undefined, + undefined, + undefined, + undefined, + 'http://malicious:1234', + ), + }, + }; + + const config = await loadCliConfig(settings, [], 'test-session', argv); + + expect(consoleWarnSpy).not.toHaveBeenCalled(); + + const mcpServers = config.getMcpServers(); + expect(mcpServers['_ide_server']).toBeDefined(); + expect(mcpServers['_ide_server'].url).toBe('http://malicious:1234'); + + consoleWarnSpy.mockRestore(); + }); }); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 69708a61..83549139 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -306,10 +306,15 @@ export async function loadCliConfig( } if (ideMode) { + if (mcpServers[IDE_SERVER_NAME]) { + logger.warn( + `Ignoring user-defined MCP server config for "${IDE_SERVER_NAME}" as it is a reserved name.`, + ); + } const companionPort = process.env.GEMINI_CLI_IDE_SERVER_PORT; if (!companionPort) { throw new Error( - "Could not run in ide mode, make sure you're running in vs code integrated terminal. Try running in a fresh terminal.", + 'Could not connect to IDE. Make sure you have the companion VS Code extension installed from the marketplace or via /ide install.', ); } const httpUrl = `http://localhost:${companionPort}/mcp`; diff --git a/packages/core/src/services/ideContext.test.ts b/packages/core/src/services/ideContext.test.ts index 0e6ff045..9aa4c013 100644 --- a/packages/core/src/services/ideContext.test.ts +++ b/packages/core/src/services/ideContext.test.ts @@ -122,4 +122,19 @@ describe('ideContext - Active File', () => { const activeFile = ideContext.getActiveFileContext(); expect(activeFile).toEqual(testFile); }); + + it('should clear the active file context', () => { + const testFile = { + filePath: '/path/to/test/file.ts', + cursor: { line: 5, character: 10 }, + }; + + ideContext.setActiveFileContext(testFile); + + expect(ideContext.getActiveFileContext()).toEqual(testFile); + + ideContext.clearActiveFileContext(); + + expect(ideContext.getActiveFileContext()).toBeUndefined(); + }); }); diff --git a/packages/core/src/services/ideContext.ts b/packages/core/src/services/ideContext.ts index 6bbe8cb9..b5f0b4af 100644 --- a/packages/core/src/services/ideContext.ts +++ b/packages/core/src/services/ideContext.ts @@ -68,6 +68,14 @@ export function createIdeContextStore() { notifySubscribers(); } + /** + * Clears the active file context and notifies all registered subscribers of the change. + */ + function clearActiveFileContext(): void { + activeFileContext = undefined; + notifySubscribers(); + } + /** * Retrieves the current active file context. * @returns The `ActiveFile` object if a file is active, otherwise `undefined`. @@ -96,6 +104,7 @@ export function createIdeContextStore() { setActiveFileContext, getActiveFileContext, subscribeToActiveFile, + clearActiveFileContext, }; } diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index 24c975c3..5849884d 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -215,6 +215,9 @@ export async function connectAndDiscover( mcpClient.onerror = (error) => { console.error(`MCP ERROR (${mcpServerName}):`, error.toString()); updateMCPServerStatus(mcpServerName, MCPServerStatus.DISCONNECTED); + if (mcpServerName === IDE_SERVER_NAME) { + ideContext.clearActiveFileContext(); + } }; if (mcpServerName === IDE_SERVER_NAME) {