Download packages/cli/src/commands/mcp/list.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 9.03 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/commands/mcp/list.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/commands/mcp/list.ts
-
curl -L -o list.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/commands/mcp/list.ts
9.03 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| // File for 'gemini mcp list' command | |
| import type { CommandModule } from 'yargs'; | |
| import { | |
| type MergedSettings, | |
| loadSettings, | |
| type LoadedSettings, | |
| } from '../../config/settings.js'; | |
| import { | |
| MCPServerStatus, | |
| createTransport, | |
| debugLogger, | |
| applyAdminAllowlist, | |
| getAdminBlockedMcpServersMessage, | |
| } from '@google/gemini-cli-core'; | |
| import type { MCPServerConfig } from '@google/gemini-cli-core'; | |
| import { Client } from '@modelcontextprotocol/sdk/client/index.js'; | |
| import { ExtensionManager } from '../../config/extension-manager.js'; | |
| import { | |
| canLoadServer, | |
| McpServerEnablementManager, | |
| } from '../../config/mcp/index.js'; | |
| import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; | |
| import { promptForSetting } from '../../config/extensions/extensionSettings.js'; | |
| import { exitCli } from '../utils.js'; | |
| import chalk from 'chalk'; | |
| export async function getMcpServersFromConfig( | |
| settings?: MergedSettings, | |
| ): Promise<{ | |
| mcpServers: Record<string, MCPServerConfig>; | |
| blockedServerNames: string[]; | |
| }> { | |
| if (!settings) { | |
| settings = loadSettings().merged; | |
| } | |
| const extensionManager = new ExtensionManager({ | |
| settings, | |
| workspaceDir: process.cwd(), | |
| requestConsent: requestConsentNonInteractive, | |
| requestSetting: promptForSetting, | |
| }); | |
| const extensions = await extensionManager.loadExtensions(); | |
| const mcpServers = { ...settings.mcpServers }; | |
| for (const extension of extensions) { | |
| Object.entries(extension.mcpServers || {}).forEach(([key, server]) => { | |
| if (mcpServers[key]) { | |
| return; | |
| } | |
| mcpServers[key] = { | |
| // eslint-disable-next-line @typescript-eslint/no-misused-spread | |
| ...server, | |
| extension, | |
| }; | |
| }); | |
| } | |
| const adminAllowlist = settings.admin?.mcp?.config; | |
| const filteredResult = applyAdminAllowlist(mcpServers, adminAllowlist); | |
| return filteredResult; | |
| } | |
| const MCP_LIST_DEFAULT_TIMEOUT_MSEC = 5000; | |
| async function testMCPConnection( | |
| serverName: string, | |
| config: MCPServerConfig, | |
| isTrusted: boolean, | |
| activeSettings: MergedSettings, | |
| ): Promise<MCPServerStatus> { | |
| // SECURITY: Only test connection if workspace is trusted or if it's a remote server. | |
| // stdio servers execute local commands and must never run in untrusted workspaces. | |
| const isStdio = !!config.command; | |
| if (isStdio && !isTrusted) { | |
| return MCPServerStatus.DISCONNECTED; | |
| } | |
| const client = new Client({ | |
| name: 'mcp-test-client', | |
| version: '0.0.1', | |
| }); | |
| const mcpContext = { | |
| sanitizationConfig: { | |
| enableEnvironmentVariableRedaction: true, | |
| allowedEnvironmentVariables: [], | |
| blockedEnvironmentVariables: activeSettings.advanced.excludedEnvVars, | |
| }, | |
| emitMcpDiagnostic: ( | |
| severity: 'info' | 'warning' | 'error', | |
| message: string, | |
| error?: unknown, | |
| serverName?: string, | |
| ) => { | |
| // In non-interactive list, we log everything through debugLogger for consistency | |
| if (severity === 'error') { | |
| debugLogger.error( | |
| chalk.red(`Error${serverName ? ` (${serverName})` : ''}: ${message}`), | |
| error, | |
| ); | |
| } else if (severity === 'warning') { | |
| debugLogger.warn( | |
| chalk.yellow( | |
| `Warning${serverName ? ` (${serverName})` : ''}: ${message}`, | |
| ), | |
| error, | |
| ); | |
| } else { | |
| debugLogger.log(message, error); | |
| } | |
| }, | |
| isTrustedFolder: () => isTrusted, | |
| }; | |
| let transport; | |
| try { | |
| // Use the same transport creation logic as core | |
| transport = await createTransport(serverName, config, false, mcpContext); | |
| } catch { | |
| await client.close(); | |
| return MCPServerStatus.DISCONNECTED; | |
| } | |
| try { | |
| // Attempt actual MCP connection with timeout from config or default to 5s. | |
| // We use a short default for the list command to keep it responsive. | |
| const timeout = config.timeout ?? MCP_LIST_DEFAULT_TIMEOUT_MSEC; | |
| await client.connect(transport, { timeout }); | |
| // Test basic MCP protocol by pinging the server. | |
| // Ping is optional per MCP spec - some servers (e.g. Google first-party) | |
| // don't implement it. A successful connect() is sufficient proof of connectivity. | |
| try { | |
| await client.ping({ timeout }); | |
| } catch (e) { | |
| debugLogger.debug( | |
| `MCP ping failed for ${serverName}, but connect succeeded:`, | |
| e, | |
| ); | |
| } | |
| await client.close(); | |
| return MCPServerStatus.CONNECTED; | |
| } catch { | |
| await transport.close(); | |
| return MCPServerStatus.DISCONNECTED; | |
| } | |
| } | |
| async function getServerStatus( | |
| serverName: string, | |
| server: MCPServerConfig, | |
| isTrusted: boolean, | |
| activeSettings: MergedSettings, | |
| consolidatedExcluded: string[], | |
| consolidatedAllowed: string[] | undefined, | |
| ): Promise<MCPServerStatus> { | |
| const mcpEnablementManager = McpServerEnablementManager.getInstance(); | |
| const loadResult = await canLoadServer(serverName, { | |
| adminMcpEnabled: activeSettings.admin?.mcp?.enabled ?? true, | |
| allowedList: consolidatedAllowed, | |
| excludedList: | |
| consolidatedExcluded.length > 0 ? consolidatedExcluded : undefined, | |
| enablement: mcpEnablementManager.getEnablementCallbacks(), | |
| }); | |
| if (!loadResult.allowed) { | |
| if ( | |
| loadResult.blockType === 'admin' || | |
| loadResult.blockType === 'allowlist' || | |
| loadResult.blockType === 'excludelist' | |
| ) { | |
| return MCPServerStatus.BLOCKED; | |
| } | |
| return MCPServerStatus.DISABLED; | |
| } | |
| if (!isTrusted) { | |
| return MCPServerStatus.DISABLED; | |
| } | |
| // Test all server types by attempting actual connection | |
| return testMCPConnection(serverName, server, isTrusted, activeSettings); | |
| } | |
| export async function listMcpServers( | |
| loadedSettingsArg?: LoadedSettings, | |
| ): Promise<void> { | |
| const loadedSettings = loadedSettingsArg ?? loadSettings(); | |
| const activeSettings = loadedSettings.merged; | |
| // If the folder is untrusted, we want to show all configured servers (including | |
| // project-scoped ones) as disabled. | |
| const allSettings = !loadedSettings.isTrusted | |
| ? loadedSettings.getMergedSettingsAsIfTrusted() | |
| : activeSettings; | |
| const { mcpServers, blockedServerNames } = | |
| await getMcpServersFromConfig(allSettings); | |
| const serverNames = Object.keys(mcpServers); | |
| if (blockedServerNames.length > 0) { | |
| const message = getAdminBlockedMcpServersMessage( | |
| blockedServerNames, | |
| undefined, | |
| ); | |
| debugLogger.log(chalk.yellow(message + '\n')); | |
| } | |
| if (serverNames.length === 0) { | |
| if (blockedServerNames.length === 0) { | |
| debugLogger.log('No MCP servers configured.'); | |
| } | |
| return; | |
| } | |
| if (!loadedSettings.isTrusted) { | |
| debugLogger.log( | |
| chalk.yellow( | |
| 'Warning: MCP servers are configured but disabled because this folder is untrusted.\n' + | |
| 'User-level servers are also suppressed in untrusted folders to prevent accidental side-effects.\n', | |
| ), | |
| ); | |
| } | |
| const consolidatedExcluded = | |
| loadedSettings.getConsolidatedExcludedMcpServers(); | |
| const consolidatedAllowed = loadedSettings.getConsolidatedAllowedMcpServers(); | |
| debugLogger.log('Configured MCP servers:\n'); | |
| for (const serverName of serverNames) { | |
| const server = mcpServers[serverName]; | |
| const status = await getServerStatus( | |
| serverName, | |
| server, | |
| loadedSettings.isTrusted, | |
| activeSettings, | |
| consolidatedExcluded, | |
| consolidatedAllowed, | |
| ); | |
| let statusIndicator = ''; | |
| let statusText = ''; | |
| switch (status) { | |
| case MCPServerStatus.CONNECTED: | |
| statusIndicator = chalk.green('✓'); | |
| statusText = 'Connected'; | |
| break; | |
| case MCPServerStatus.CONNECTING: | |
| statusIndicator = chalk.yellow('…'); | |
| statusText = 'Connecting'; | |
| break; | |
| case MCPServerStatus.BLOCKED: | |
| statusIndicator = chalk.red('⛔'); | |
| statusText = 'Blocked'; | |
| break; | |
| case MCPServerStatus.DISABLED: | |
| statusIndicator = chalk.gray('○'); | |
| statusText = 'Disabled'; | |
| break; | |
| case MCPServerStatus.DISCONNECTED: | |
| default: | |
| statusIndicator = chalk.red('✗'); | |
| statusText = 'Disconnected'; | |
| break; | |
| } | |
| let serverInfo = | |
| serverName + | |
| (server.extension?.name ? ` (from ${server.extension.name})` : '') + | |
| ': '; | |
| if (server.httpUrl) { | |
| serverInfo += `${server.httpUrl} (http)`; | |
| } else if (server.url) { | |
| const type = server.type || 'http'; | |
| serverInfo += `${server.url} (${type})`; | |
| } else if (server.command) { | |
| serverInfo += `${server.command} ${server.args?.join(' ') || ''} (stdio)`; | |
| } | |
| debugLogger.log(`${statusIndicator} ${serverInfo} - ${statusText}`); | |
| } | |
| } | |
| interface ListArgs { | |
| loadedSettings?: LoadedSettings; | |
| } | |
| export const listCommand: CommandModule<object, ListArgs> = { | |
| command: 'list', | |
| describe: 'List all configured MCP servers', | |
| handler: async (argv) => { | |
| await listMcpServers(argv.loadedSettings); | |
| await exitCli(); | |
| }, | |
| }; | |