mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-20 18:14:29 -07:00
Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
@@ -23,6 +23,12 @@ import {
|
||||
} from '@google/gemini-cli-core';
|
||||
import { appEvents, AppEvent } from '../../utils/events.js';
|
||||
import { MessageType, type HistoryItemMcpStatus } from '../types.js';
|
||||
import {
|
||||
McpServerEnablementManager,
|
||||
normalizeServerId,
|
||||
canLoadServer,
|
||||
} from '../../config/mcp/mcpServerEnablement.js';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
|
||||
const authCommand: SlashCommand = {
|
||||
name: 'auth',
|
||||
@@ -241,6 +247,14 @@ const listAction = async (
|
||||
}
|
||||
}
|
||||
|
||||
// Get enablement state for all servers
|
||||
const enablementManager = McpServerEnablementManager.getInstance();
|
||||
const enablementState: HistoryItemMcpStatus['enablementState'] = {};
|
||||
for (const serverName of serverNames) {
|
||||
enablementState[serverName] =
|
||||
await enablementManager.getDisplayState(serverName);
|
||||
}
|
||||
|
||||
const mcpStatusItem: HistoryItemMcpStatus = {
|
||||
type: MessageType.MCP_STATUS,
|
||||
servers: mcpServers,
|
||||
@@ -263,6 +277,7 @@ const listAction = async (
|
||||
description: resource.description,
|
||||
})),
|
||||
authStatus,
|
||||
enablementState,
|
||||
blockedServers: blockedMcpServers,
|
||||
discoveryInProgress,
|
||||
connectingServers,
|
||||
@@ -346,6 +361,156 @@ const refreshCommand: SlashCommand = {
|
||||
},
|
||||
};
|
||||
|
||||
async function handleEnableDisable(
|
||||
context: CommandContext,
|
||||
args: string,
|
||||
enable: boolean,
|
||||
): Promise<MessageActionReturn> {
|
||||
const { config } = context.services;
|
||||
if (!config) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Config not loaded.',
|
||||
};
|
||||
}
|
||||
|
||||
const parts = args.trim().split(/\s+/);
|
||||
const isSession = parts.includes('--session');
|
||||
const serverName = parts.filter((p) => p !== '--session')[0];
|
||||
const action = enable ? 'enable' : 'disable';
|
||||
|
||||
if (!serverName) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: `Server name required. Usage: /mcp ${action} <server-name> [--session]`,
|
||||
};
|
||||
}
|
||||
|
||||
const name = normalizeServerId(serverName);
|
||||
|
||||
// Validate server exists
|
||||
const servers = config.getMcpClientManager()?.getMcpServers() || {};
|
||||
const normalizedServerNames = Object.keys(servers).map(normalizeServerId);
|
||||
if (!normalizedServerNames.includes(name)) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: `Server '${serverName}' not found. Use /mcp list to see available servers.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if server is from an extension
|
||||
const serverKey = Object.keys(servers).find(
|
||||
(key) => normalizeServerId(key) === name,
|
||||
);
|
||||
const server = serverKey ? servers[serverKey] : undefined;
|
||||
if (server?.extension) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: `Server '${serverName}' is provided by extension '${server.extension.name}'.\nUse '/extensions ${action} ${server.extension.name}' to manage this extension.`,
|
||||
};
|
||||
}
|
||||
|
||||
const manager = McpServerEnablementManager.getInstance();
|
||||
|
||||
if (enable) {
|
||||
const settings = loadSettings();
|
||||
const result = await canLoadServer(name, {
|
||||
adminMcpEnabled: settings.merged.admin?.mcp?.enabled ?? true,
|
||||
allowedList: settings.merged.mcp?.allowed,
|
||||
excludedList: settings.merged.mcp?.excluded,
|
||||
});
|
||||
if (
|
||||
!result.allowed &&
|
||||
(result.blockType === 'allowlist' || result.blockType === 'excludelist')
|
||||
) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: result.reason ?? 'Blocked by settings.',
|
||||
};
|
||||
}
|
||||
if (isSession) {
|
||||
manager.clearSessionDisable(name);
|
||||
} else {
|
||||
await manager.enable(name);
|
||||
}
|
||||
if (result.blockType === 'admin') {
|
||||
context.ui.addItem(
|
||||
{
|
||||
type: 'warning',
|
||||
text: 'MCP disabled by admin. Will load when enabled.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (isSession) {
|
||||
manager.disableForSession(name);
|
||||
} else {
|
||||
await manager.disable(name);
|
||||
}
|
||||
}
|
||||
|
||||
const msg = `MCP server '${name}' ${enable ? 'enabled' : 'disabled'}${isSession ? ' for this session' : ''}.`;
|
||||
|
||||
const mcpClientManager = config.getMcpClientManager();
|
||||
if (mcpClientManager) {
|
||||
context.ui.addItem(
|
||||
{ type: 'info', text: 'Restarting MCP servers...' },
|
||||
Date.now(),
|
||||
);
|
||||
await mcpClientManager.restart();
|
||||
}
|
||||
if (config.getGeminiClient()?.isInitialized())
|
||||
await config.getGeminiClient().setTools();
|
||||
context.ui.reloadCommands();
|
||||
|
||||
return { type: 'message', messageType: 'info', content: msg };
|
||||
}
|
||||
|
||||
async function getEnablementCompletion(
|
||||
context: CommandContext,
|
||||
partialArg: string,
|
||||
showEnabled: boolean,
|
||||
): Promise<string[]> {
|
||||
const { config } = context.services;
|
||||
if (!config) return [];
|
||||
const servers = Object.keys(
|
||||
config.getMcpClientManager()?.getMcpServers() || {},
|
||||
);
|
||||
const manager = McpServerEnablementManager.getInstance();
|
||||
const results: string[] = [];
|
||||
for (const n of servers) {
|
||||
const state = await manager.getDisplayState(n);
|
||||
if (state.enabled === showEnabled && n.startsWith(partialArg)) {
|
||||
results.push(n);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
const enableCommand: SlashCommand = {
|
||||
name: 'enable',
|
||||
description: 'Enable a disabled MCP server',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: (ctx, args) => handleEnableDisable(ctx, args, true),
|
||||
completion: (ctx, arg) => getEnablementCompletion(ctx, arg, false),
|
||||
};
|
||||
|
||||
const disableCommand: SlashCommand = {
|
||||
name: 'disable',
|
||||
description: 'Disable an MCP server',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
action: (ctx, args) => handleEnableDisable(ctx, args, false),
|
||||
completion: (ctx, arg) => getEnablementCompletion(ctx, arg, true),
|
||||
};
|
||||
|
||||
export const mcpCommand: SlashCommand = {
|
||||
name: 'mcp',
|
||||
description: 'Manage configured Model Context Protocol (MCP) servers',
|
||||
@@ -357,6 +522,8 @@ export const mcpCommand: SlashCommand = {
|
||||
schemaCommand,
|
||||
authCommand,
|
||||
refreshCommand,
|
||||
enableCommand,
|
||||
disableCommand,
|
||||
],
|
||||
action: async (context: CommandContext) => listAction(context),
|
||||
};
|
||||
|
||||
@@ -40,6 +40,13 @@ describe('McpStatus', () => {
|
||||
blockedServers: [],
|
||||
serverStatus: () => MCPServerStatus.CONNECTED,
|
||||
authStatus: {},
|
||||
enablementState: {
|
||||
'server-1': {
|
||||
enabled: true,
|
||||
isSessionDisabled: false,
|
||||
isPersistentDisabled: false,
|
||||
},
|
||||
},
|
||||
discoveryInProgress: false,
|
||||
connectingServers: [],
|
||||
showDescriptions: true,
|
||||
|
||||
@@ -25,6 +25,7 @@ interface McpStatusProps {
|
||||
blockedServers: Array<{ name: string; extensionName: string }>;
|
||||
serverStatus: (serverName: string) => MCPServerStatus;
|
||||
authStatus: HistoryItemMcpStatus['authStatus'];
|
||||
enablementState: HistoryItemMcpStatus['enablementState'];
|
||||
discoveryInProgress: boolean;
|
||||
connectingServers: string[];
|
||||
showDescriptions: boolean;
|
||||
@@ -39,6 +40,7 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
||||
blockedServers,
|
||||
serverStatus,
|
||||
authStatus,
|
||||
enablementState,
|
||||
discoveryInProgress,
|
||||
connectingServers,
|
||||
showDescriptions,
|
||||
@@ -104,23 +106,35 @@ export const McpStatus: React.FC<McpStatusProps> = ({
|
||||
let statusText = '';
|
||||
let statusColor = theme.text.primary;
|
||||
|
||||
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;
|
||||
// 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;
|
||||
|
||||
@@ -270,6 +270,14 @@ export type HistoryItemMcpStatus = HistoryItemBase & {
|
||||
string,
|
||||
'authenticated' | 'expired' | 'unauthenticated' | 'not-configured'
|
||||
>;
|
||||
enablementState: Record<
|
||||
string,
|
||||
{
|
||||
enabled: boolean;
|
||||
isSessionDisabled: boolean;
|
||||
isPersistentDisabled: boolean;
|
||||
}
|
||||
>;
|
||||
blockedServers: Array<{ name: string; extensionName: string }>;
|
||||
discoveryInProgress: boolean;
|
||||
connectingServers: string[];
|
||||
|
||||
Reference in New Issue
Block a user