mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-30 15:04:16 -07:00
Add MCP loading indicator when initializing Gemini CLI (#6923)
This commit is contained in:
@@ -38,6 +38,7 @@ import { annotateActiveExtensions } from './extension.js';
|
|||||||
import { getCliVersion } from '../utils/version.js';
|
import { getCliVersion } from '../utils/version.js';
|
||||||
import { loadSandboxConfig } from './sandboxConfig.js';
|
import { loadSandboxConfig } from './sandboxConfig.js';
|
||||||
import { resolvePath } from '../utils/resolvePath.js';
|
import { resolvePath } from '../utils/resolvePath.js';
|
||||||
|
import { appEvents } from '../utils/events.js';
|
||||||
|
|
||||||
import { isWorkspaceTrusted } from './trustedFolders.js';
|
import { isWorkspaceTrusted } from './trustedFolders.js';
|
||||||
|
|
||||||
@@ -568,6 +569,7 @@ export async function loadCliConfig(
|
|||||||
shouldUseNodePtyShell: settings.tools?.usePty,
|
shouldUseNodePtyShell: settings.tools?.usePty,
|
||||||
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
|
skipNextSpeakerCheck: settings.model?.skipNextSpeakerCheck,
|
||||||
enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
|
enablePromptCompletion: settings.general?.enablePromptCompletion ?? false,
|
||||||
|
eventEmitter: appEvents,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { render } from 'ink';
|
import { render, Box, Text } from 'ink';
|
||||||
|
import Spinner from 'ink-spinner';
|
||||||
import { AppWrapper } from './ui/App.js';
|
import { AppWrapper } from './ui/App.js';
|
||||||
import { loadCliConfig, parseArguments } from './config/config.js';
|
import { loadCliConfig, parseArguments } from './config/config.js';
|
||||||
import { readStdin } from './utils/readStdin.js';
|
import { readStdin } from './utils/readStdin.js';
|
||||||
@@ -105,6 +106,39 @@ async function relaunchWithAdditionalArgs(additionalArgs: string[]) {
|
|||||||
await new Promise((resolve) => child.on('close', resolve));
|
await new Promise((resolve) => child.on('close', resolve));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const InitializingComponent = ({ initialTotal }: { initialTotal: number }) => {
|
||||||
|
const [total, setTotal] = useState(initialTotal);
|
||||||
|
const [connected, setConnected] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onStart = ({ count }: { count: number }) => setTotal(count);
|
||||||
|
const onChange = () => {
|
||||||
|
setConnected((val) => val + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
appEvents.on('mcp-servers-discovery-start', onStart);
|
||||||
|
appEvents.on('mcp-server-connected', onChange);
|
||||||
|
appEvents.on('mcp-server-error', onChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
appEvents.off('mcp-servers-discovery-start', onStart);
|
||||||
|
appEvents.off('mcp-server-connected', onChange);
|
||||||
|
appEvents.off('mcp-server-error', onChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const message = `Connecting to MCP servers... (${connected}/${total})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Text>
|
||||||
|
<Spinner /> {message}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
import { runZedIntegration } from './zed-integration/zedIntegration.js';
|
import { runZedIntegration } from './zed-integration/zedIntegration.js';
|
||||||
|
|
||||||
export function setupUnhandledRejectionHandler() {
|
export function setupUnhandledRejectionHandler() {
|
||||||
@@ -238,8 +272,25 @@ export async function main() {
|
|||||||
|
|
||||||
setMaxSizedBoxDebugging(config.getDebugMode());
|
setMaxSizedBoxDebugging(config.getDebugMode());
|
||||||
|
|
||||||
|
const mcpServers = config.getMcpServers();
|
||||||
|
const mcpServersCount = mcpServers ? Object.keys(mcpServers).length : 0;
|
||||||
|
|
||||||
|
let spinnerInstance;
|
||||||
|
if (config.isInteractive() && mcpServersCount > 0) {
|
||||||
|
spinnerInstance = render(
|
||||||
|
<InitializingComponent initialTotal={mcpServersCount} />,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await config.initialize();
|
await config.initialize();
|
||||||
|
|
||||||
|
if (spinnerInstance) {
|
||||||
|
// Small UX detail to show the completion message for a bit before unmounting.
|
||||||
|
await new Promise((f) => setTimeout(f, 100));
|
||||||
|
spinnerInstance.clear();
|
||||||
|
spinnerInstance.unmount();
|
||||||
|
}
|
||||||
|
|
||||||
if (config.getIdeMode()) {
|
if (config.getIdeMode()) {
|
||||||
await config.getIdeClient().connect();
|
await config.getIdeClient().connect();
|
||||||
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.START));
|
logIdeConnection(config, new IdeConnectionEvent(IdeConnectionType.START));
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import type { AnyToolInvocation } from '../tools/tools.js';
|
|||||||
import { WorkspaceContext } from '../utils/workspaceContext.js';
|
import { WorkspaceContext } from '../utils/workspaceContext.js';
|
||||||
import { Storage } from './storage.js';
|
import { Storage } from './storage.js';
|
||||||
import { FileExclusions } from '../utils/ignorePatterns.js';
|
import { FileExclusions } from '../utils/ignorePatterns.js';
|
||||||
|
import type { EventEmitter } from 'node:events';
|
||||||
|
|
||||||
export enum ApprovalMode {
|
export enum ApprovalMode {
|
||||||
DEFAULT = 'default',
|
DEFAULT = 'default',
|
||||||
@@ -207,6 +208,7 @@ export interface ConfigParameters {
|
|||||||
skipNextSpeakerCheck?: boolean;
|
skipNextSpeakerCheck?: boolean;
|
||||||
extensionManagement?: boolean;
|
extensionManagement?: boolean;
|
||||||
enablePromptCompletion?: boolean;
|
enablePromptCompletion?: boolean;
|
||||||
|
eventEmitter?: EventEmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
@@ -282,6 +284,7 @@ export class Config {
|
|||||||
private initialized: boolean = false;
|
private initialized: boolean = false;
|
||||||
readonly storage: Storage;
|
readonly storage: Storage;
|
||||||
private readonly fileExclusions: FileExclusions;
|
private readonly fileExclusions: FileExclusions;
|
||||||
|
private readonly eventEmitter?: EventEmitter;
|
||||||
|
|
||||||
constructor(params: ConfigParameters) {
|
constructor(params: ConfigParameters) {
|
||||||
this.sessionId = params.sessionId;
|
this.sessionId = params.sessionId;
|
||||||
@@ -356,6 +359,7 @@ export class Config {
|
|||||||
this.storage = new Storage(this.targetDir);
|
this.storage = new Storage(this.targetDir);
|
||||||
this.enablePromptCompletion = params.enablePromptCompletion ?? false;
|
this.enablePromptCompletion = params.enablePromptCompletion ?? false;
|
||||||
this.fileExclusions = new FileExclusions(this);
|
this.fileExclusions = new FileExclusions(this);
|
||||||
|
this.eventEmitter = params.eventEmitter;
|
||||||
|
|
||||||
if (params.contextFileName) {
|
if (params.contextFileName) {
|
||||||
setGeminiMdFilename(params.contextFileName);
|
setGeminiMdFilename(params.contextFileName);
|
||||||
@@ -803,7 +807,7 @@ export class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createToolRegistry(): Promise<ToolRegistry> {
|
async createToolRegistry(): Promise<ToolRegistry> {
|
||||||
const registry = new ToolRegistry(this);
|
const registry = new ToolRegistry(this, this.eventEmitter);
|
||||||
|
|
||||||
// helper to create & register core tools that are enabled
|
// helper to create & register core tools that are enabled
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
populateMcpServerCommand,
|
populateMcpServerCommand,
|
||||||
} from './mcp-client.js';
|
} from './mcp-client.js';
|
||||||
import { getErrorMessage } from '../utils/errors.js';
|
import { getErrorMessage } from '../utils/errors.js';
|
||||||
|
import type { EventEmitter } from 'node:events';
|
||||||
import type { WorkspaceContext } from '../utils/workspaceContext.js';
|
import type { WorkspaceContext } from '../utils/workspaceContext.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +30,7 @@ export class McpClientManager {
|
|||||||
private readonly debugMode: boolean;
|
private readonly debugMode: boolean;
|
||||||
private readonly workspaceContext: WorkspaceContext;
|
private readonly workspaceContext: WorkspaceContext;
|
||||||
private discoveryState: MCPDiscoveryState = MCPDiscoveryState.NOT_STARTED;
|
private discoveryState: MCPDiscoveryState = MCPDiscoveryState.NOT_STARTED;
|
||||||
|
private readonly eventEmitter?: EventEmitter;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
mcpServers: Record<string, MCPServerConfig>,
|
mcpServers: Record<string, MCPServerConfig>,
|
||||||
@@ -37,6 +39,7 @@ export class McpClientManager {
|
|||||||
promptRegistry: PromptRegistry,
|
promptRegistry: PromptRegistry,
|
||||||
debugMode: boolean,
|
debugMode: boolean,
|
||||||
workspaceContext: WorkspaceContext,
|
workspaceContext: WorkspaceContext,
|
||||||
|
eventEmitter?: EventEmitter,
|
||||||
) {
|
) {
|
||||||
this.mcpServers = mcpServers;
|
this.mcpServers = mcpServers;
|
||||||
this.mcpServerCommand = mcpServerCommand;
|
this.mcpServerCommand = mcpServerCommand;
|
||||||
@@ -44,6 +47,7 @@ export class McpClientManager {
|
|||||||
this.promptRegistry = promptRegistry;
|
this.promptRegistry = promptRegistry;
|
||||||
this.debugMode = debugMode;
|
this.debugMode = debugMode;
|
||||||
this.workspaceContext = workspaceContext;
|
this.workspaceContext = workspaceContext;
|
||||||
|
this.eventEmitter = eventEmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,14 +57,28 @@ export class McpClientManager {
|
|||||||
*/
|
*/
|
||||||
async discoverAllMcpTools(): Promise<void> {
|
async discoverAllMcpTools(): Promise<void> {
|
||||||
await this.stop();
|
await this.stop();
|
||||||
this.discoveryState = MCPDiscoveryState.IN_PROGRESS;
|
|
||||||
const servers = populateMcpServerCommand(
|
const servers = populateMcpServerCommand(
|
||||||
this.mcpServers,
|
this.mcpServers,
|
||||||
this.mcpServerCommand,
|
this.mcpServerCommand,
|
||||||
);
|
);
|
||||||
|
|
||||||
const discoveryPromises = Object.entries(servers).map(
|
const serverEntries = Object.entries(servers);
|
||||||
async ([name, config]) => {
|
const total = serverEntries.length;
|
||||||
|
|
||||||
|
this.eventEmitter?.emit('mcp-servers-discovery-start', { count: total });
|
||||||
|
|
||||||
|
this.discoveryState = MCPDiscoveryState.IN_PROGRESS;
|
||||||
|
|
||||||
|
const discoveryPromises = serverEntries.map(
|
||||||
|
async ([name, config], index) => {
|
||||||
|
const current = index + 1;
|
||||||
|
this.eventEmitter?.emit('mcp-server-connecting', {
|
||||||
|
name,
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
|
||||||
const client = new McpClient(
|
const client = new McpClient(
|
||||||
name,
|
name,
|
||||||
config,
|
config,
|
||||||
@@ -70,10 +88,22 @@ export class McpClientManager {
|
|||||||
this.debugMode,
|
this.debugMode,
|
||||||
);
|
);
|
||||||
this.clients.set(name, client);
|
this.clients.set(name, client);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.connect();
|
await client.connect();
|
||||||
await client.discover();
|
await client.discover();
|
||||||
|
this.eventEmitter?.emit('mcp-server-connected', {
|
||||||
|
name,
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.eventEmitter?.emit('mcp-server-error', {
|
||||||
|
name,
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
error,
|
||||||
|
});
|
||||||
// Log the error but don't let a single failed server stop the others
|
// Log the error but don't let a single failed server stop the others
|
||||||
console.error(
|
console.error(
|
||||||
`Error during discovery for server '${name}': ${getErrorMessage(
|
`Error during discovery for server '${name}': ${getErrorMessage(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { DiscoveredMCPTool } from './mcp-tool.js';
|
|||||||
import { parse } from 'shell-quote';
|
import { parse } from 'shell-quote';
|
||||||
import { ToolErrorType } from './tool-error.js';
|
import { ToolErrorType } from './tool-error.js';
|
||||||
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
|
import { safeJsonStringify } from '../utils/safeJsonStringify.js';
|
||||||
|
import type { EventEmitter } from 'node:events';
|
||||||
|
|
||||||
type ToolParams = Record<string, unknown>;
|
type ToolParams = Record<string, unknown>;
|
||||||
|
|
||||||
@@ -170,7 +171,7 @@ export class ToolRegistry {
|
|||||||
private config: Config;
|
private config: Config;
|
||||||
private mcpClientManager: McpClientManager;
|
private mcpClientManager: McpClientManager;
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config, eventEmitter?: EventEmitter) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.mcpClientManager = new McpClientManager(
|
this.mcpClientManager = new McpClientManager(
|
||||||
this.config.getMcpServers() ?? {},
|
this.config.getMcpServers() ?? {},
|
||||||
@@ -179,6 +180,7 @@ export class ToolRegistry {
|
|||||||
this.config.getPromptRegistry(),
|
this.config.getPromptRegistry(),
|
||||||
this.config.getDebugMode(),
|
this.config.getDebugMode(),
|
||||||
this.config.getWorkspaceContext(),
|
this.config.getWorkspaceContext(),
|
||||||
|
eventEmitter,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user