/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import type { MCPServerConfig } from '@qwen-code/qwen-code-core'; import { MCPServerStatus } from '@qwen-code/qwen-code-core'; import { Box, Text } from 'ink'; import type React from 'react'; import { theme } from '../../semantic-colors.js'; import type { HistoryItemMcpStatus, JsonMcpPrompt, JsonMcpTool, } from '../../types.js'; import { t } from '../../../i18n/index.js'; interface McpStatusProps { servers: Record; tools: JsonMcpTool[]; prompts: JsonMcpPrompt[]; blockedServers: Array<{ name: string; extensionName: string }>; serverStatus: (serverName: string) => MCPServerStatus; authStatus: HistoryItemMcpStatus['authStatus']; discoveryInProgress: boolean; connectingServers: string[]; showDescriptions: boolean; showSchema: boolean; showTips: boolean; } export const McpStatus: React.FC = ({ servers, tools, prompts, blockedServers, serverStatus, authStatus, discoveryInProgress, connectingServers, showDescriptions, showSchema, showTips, }) => { const serverNames = Object.keys(servers); if (serverNames.length === 0 && blockedServers.length === 0) { return ( {t('No MCP servers configured.')} {t('Please view MCP documentation in your browser:')}{' '} https://goo.gle/gemini-cli-docs-mcp {' '} {t('or use the cli /docs command')} ); } return ( {discoveryInProgress && ( {t('⏳ MCP servers are starting up ({{count}} initializing)...', { count: String(connectingServers.length), })} {t( 'Note: First startup may take longer. Tool availability will update automatically.', )} )} {t('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 originalStatus = serverStatus(serverName); const hasCachedItems = serverTools.length > 0 || serverPrompts.length > 0; const status = originalStatus === MCPServerStatus.DISCONNECTED && hasCachedItems ? MCPServerStatus.CONNECTED : originalStatus; let statusIndicator = ''; let statusText = ''; let statusColor = theme.text.primary; switch (status) { case MCPServerStatus.CONNECTED: statusIndicator = '🟢'; statusText = t('Ready'); statusColor = theme.status.success; break; case MCPServerStatus.CONNECTING: statusIndicator = '🔄'; statusText = t('Starting... (first startup may take longer)'); statusColor = theme.status.warning; break; case MCPServerStatus.DISCONNECTED: default: statusIndicator = '🔴'; statusText = t('Disconnected'); statusColor = theme.status.error; break; } let serverDisplayName = serverName; if (server.extensionName) { serverDisplayName += ` ${t('(from {{extensionName}})', { extensionName: server.extensionName, })}`; } const toolCount = serverTools.length; const promptCount = serverPrompts.length; const parts = []; if (toolCount > 0) { parts.push( toolCount === 1 ? t('{{count}} tool', { count: String(toolCount) }) : t('{{count}} tools', { count: String(toolCount) }), ); } if (promptCount > 0) { parts.push( promptCount === 1 ? t('{{count}} prompt', { count: String(promptCount) }) : t('{{count}} prompts', { count: String(promptCount) }), ); } const serverAuthStatus = authStatus[serverName]; let authStatusNode: React.ReactNode = null; if (serverAuthStatus === 'authenticated') { authStatusNode = ({t('OAuth')}); } else if (serverAuthStatus === 'expired') { authStatusNode = ( ({t('OAuth expired')}) ); } else if (serverAuthStatus === 'unauthenticated') { authStatusNode = ( {' '} ({t('OAuth not authenticated')}) ); } return ( {statusIndicator} {serverDisplayName} {' - '} {statusText} {status === MCPServerStatus.CONNECTED && parts.length > 0 && ` (${parts.join(', ')})`} {authStatusNode} {status === MCPServerStatus.CONNECTING && ( ({t('tools and prompts will appear when ready')}) )} {status === MCPServerStatus.DISCONNECTED && toolCount > 0 && ( ({t('{{count}} tools cached', { count: String(toolCount) })}) )} {showDescriptions && server?.description && ( {server.description.trim()} )} {serverTools.length > 0 && ( {t('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 && ( {t('Parameters:')} {schemaContent} )} ); })} )} {serverPrompts.length > 0 && ( {t('Prompts:')} {serverPrompts.map((prompt) => ( - {prompt.name} {showDescriptions && prompt.description && ( {prompt.description.trim()} )} ))} )} ); })} {blockedServers.map((server) => ( 🔴 {server.name} {server.extensionName ? ` ${t('(from {{extensionName}})', { extensionName: server.extensionName, })}` : ''} - {t('Blocked')} ))} {showTips && ( {t('💡 Tips:')} {' '}- {t('Use')} /mcp desc{' '} {t('to show server and tool descriptions')} {' '}- {t('Use')}{' '} /mcp schema{' '} {t('to show tool parameter schemas')} {' '}- {t('Use')}{' '} /mcp nodesc{' '} {t('to hide descriptions')} {' '}- {t('Use')}{' '} /mcp auth <server-name>{' '} {t('to authenticate with OAuth-enabled servers')} {' '}- {t('Press')} Ctrl+T{' '} {t('to toggle tool descriptions on/off')} )} ); };