/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { MCPServerConfig } from '@google/gemini-cli-core'; import { MCPServerStatus } from '@google/gemini-cli-core'; import { Box, Text } from 'ink'; import type React from 'react'; import { MAX_MCP_RESOURCES_TO_SHOW } from '../../constants.js'; import { theme } from '../../semantic-colors.js'; import type { HistoryItemMcpStatus, JsonMcpPrompt, JsonMcpResource, JsonMcpTool, } from '../../types.js'; interface McpStatusProps { servers: Record; tools: JsonMcpTool[]; prompts: JsonMcpPrompt[]; resources: JsonMcpResource[]; blockedServers: Array<{ name: string; extensionName: string }>; serverStatus: (serverName: string) => MCPServerStatus; authStatus: HistoryItemMcpStatus['authStatus']; enablementState: HistoryItemMcpStatus['enablementState']; errors: Record; discoveryInProgress: boolean; connectingServers: string[]; showDescriptions: boolean; showSchema: boolean; } export const McpStatus: React.FC = ({ servers, tools, prompts, resources, blockedServers, serverStatus, authStatus, enablementState, errors, discoveryInProgress, connectingServers, showDescriptions, showSchema, }) => { const serverNames = Object.keys(servers).filter( (serverName) => !blockedServers.some( (blockedServer) => blockedServer.name === serverName, ), ); if (serverNames.length === 0 && blockedServers.length === 0) { return ( No MCP servers configured. Please view MCP documentation in your browser:{' '} https://goo.gle/gemini-cli-docs-mcp {' '} or use the cli /docs command ); } return ( {discoveryInProgress && ( ⏳ MCP servers are starting up ({connectingServers.length}{' '} initializing)... Note: First startup may take longer. Tool availability will update automatically. )} Configured MCP servers: {serverNames.map((serverName) => { const server = servers[serverName]; const serverTools = tools.filter( (tool) => tool.serverName === serverName, ); const serverPrompts = prompts.filter( (prompt) => prompt.serverName === serverName, ); const serverResources = resources.filter( (resource) => resource.serverName === serverName, ); const originalStatus = serverStatus(serverName); const hasCachedItems = serverTools.length > 0 || serverPrompts.length > 0 || serverResources.length > 0; const status = originalStatus === MCPServerStatus.DISCONNECTED && hasCachedItems ? MCPServerStatus.CONNECTED : originalStatus; let statusIndicator = ''; let statusText = ''; let statusColor = theme.text.primary; // Check enablement state const serverEnablement = enablementState[serverName]; const isDisabled = serverEnablement && !serverEnablement.enabled; if (isDisabled) { statusIndicator = '⏸️'; statusText = serverEnablement.isSessionDisabled ? 'Disabled (session)' : 'Disabled'; statusColor = theme.text.secondary; } else { switch (status) { case MCPServerStatus.CONNECTED: statusIndicator = '🟒'; statusText = 'Ready'; statusColor = theme.status.success; break; case MCPServerStatus.CONNECTING: statusIndicator = 'πŸ”„'; statusText = 'Starting... (first startup may take longer)'; statusColor = theme.status.warning; break; case MCPServerStatus.DISCONNECTED: default: statusIndicator = 'πŸ”΄'; statusText = 'Disconnected'; statusColor = theme.status.error; break; } } let serverDisplayName = serverName; if (server.extension?.name) { serverDisplayName += ` (from ${server.extension?.name})`; } const toolCount = serverTools.length; const promptCount = serverPrompts.length; const resourceCount = serverResources.length; const parts = []; if (toolCount > 0) { parts.push(`${toolCount} ${toolCount === 1 ? 'tool' : 'tools'}`); } if (promptCount > 0) { parts.push( `${promptCount} ${promptCount === 1 ? 'prompt' : 'prompts'}`, ); } if (resourceCount > 0) { parts.push( `${resourceCount} ${resourceCount === 1 ? 'resource' : 'resources'}`, ); } const serverAuthStatus = authStatus[serverName]; let authStatusNode: React.ReactNode = null; if (serverAuthStatus === 'authenticated') { authStatusNode = (OAuth); } else if (serverAuthStatus === 'expired') { authStatusNode = ( (OAuth expired) ); } else if (serverAuthStatus === 'unauthenticated') { authStatusNode = ( (OAuth not authenticated) ); } return ( {statusIndicator} {serverDisplayName} {' - '} {statusText} {status === MCPServerStatus.CONNECTED && parts.length > 0 && ` (${parts.join(', ')})`} {authStatusNode} {status === MCPServerStatus.CONNECTING && ( (tools and prompts will appear when ready) )} {status === MCPServerStatus.DISCONNECTED && toolCount > 0 && ( ({toolCount} tools cached) )} {errors[serverName] && ( Error: {errors[serverName]} )} {showDescriptions && server?.description && ( {server.description.trim()} )} {serverTools.length > 0 && ( Tools: {serverTools.map((tool) => { const schemaContent = showSchema && tool.schema && (tool.schema.parametersJsonSchema || tool.schema.parameters) ? JSON.stringify( tool.schema.parametersJsonSchema ?? tool.schema.parameters, null, 2, ) : null; return ( - {tool.name} {showDescriptions && tool.description && ( {tool.description.trim()} )} {schemaContent && ( Parameters: {schemaContent} )} ); })} )} {serverPrompts.length > 0 && ( Prompts: {serverPrompts.map((prompt) => ( - {prompt.name} {showDescriptions && prompt.description && ( {prompt.description.trim()} )} ))} )} {serverResources.length > 0 && ( Resources: {serverResources .slice(0, MAX_MCP_RESOURCES_TO_SHOW) .map((resource, index) => { const label = resource.name || resource.uri || 'resource'; return ( - {label} {resource.uri ? ` (${resource.uri})` : ''} {resource.mimeType ? ` [${resource.mimeType}]` : ''} {showDescriptions && resource.description && ( {resource.description.trim()} )} ); })} {serverResources.length > MAX_MCP_RESOURCES_TO_SHOW && ( {' '}...{' '} {serverResources.length - MAX_MCP_RESOURCES_TO_SHOW}{' '} {serverResources.length - MAX_MCP_RESOURCES_TO_SHOW === 1 ? 'resource' : 'resources'}{' '} hidden )} )} ); })} {blockedServers.map((server) => ( πŸ”΄ {server.name} {server.extensionName ? ` (from ${server.extensionName})` : ''} - Blocked ))} ); };