mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
Summarize extensions and MCP servers on startup (#3977)
This commit is contained in:
@@ -58,6 +58,12 @@ interface MockServerConfig {
|
||||
getToolCallCommand: Mock<() => string | undefined>;
|
||||
getMcpServerCommand: Mock<() => string | undefined>;
|
||||
getMcpServers: Mock<() => Record<string, MCPServerConfig> | undefined>;
|
||||
getExtensions: Mock<
|
||||
() => Array<{ name: string; version: string; isActive: boolean }>
|
||||
>;
|
||||
getBlockedMcpServers: Mock<
|
||||
() => Array<{ name: string; extensionName: string }>
|
||||
>;
|
||||
getUserAgent: Mock<() => string>;
|
||||
getUserMemory: Mock<() => string>;
|
||||
setUserMemory: Mock<(newUserMemory: string) => void>;
|
||||
@@ -118,6 +124,8 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
getToolCallCommand: vi.fn(() => opts.toolCallCommand),
|
||||
getMcpServerCommand: vi.fn(() => opts.mcpServerCommand),
|
||||
getMcpServers: vi.fn(() => opts.mcpServers),
|
||||
getExtensions: vi.fn(() => []),
|
||||
getBlockedMcpServers: vi.fn(() => []),
|
||||
getUserAgent: vi.fn(() => opts.userAgent || 'test-agent'),
|
||||
getUserMemory: vi.fn(() => opts.userMemory || ''),
|
||||
setUserMemory: vi.fn(),
|
||||
|
||||
@@ -886,6 +886,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||
geminiMdFileCount={geminiMdFileCount}
|
||||
contextFileNames={contextFileNames}
|
||||
mcpServers={config.getMcpServers()}
|
||||
blockedMcpServers={config.getBlockedMcpServers()}
|
||||
showToolDescriptions={showToolDescriptions}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('extensionsCommand', () => {
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getActiveExtensions: () => [],
|
||||
getExtensions: () => [],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -36,13 +36,14 @@ describe('extensionsCommand', () => {
|
||||
|
||||
it('should list active extensions when they are found', async () => {
|
||||
const mockExtensions = [
|
||||
{ name: 'ext-one', version: '1.0.0' },
|
||||
{ name: 'ext-two', version: '2.1.0' },
|
||||
{ name: 'ext-one', version: '1.0.0', isActive: true },
|
||||
{ name: 'ext-two', version: '2.1.0', isActive: true },
|
||||
{ name: 'ext-three', version: '3.0.0', isActive: false },
|
||||
];
|
||||
mockContext = createMockCommandContext({
|
||||
services: {
|
||||
config: {
|
||||
getActiveExtensions: () => mockExtensions,
|
||||
getExtensions: () => mockExtensions,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,7 +11,9 @@ export const extensionsCommand: SlashCommand = {
|
||||
name: 'extensions',
|
||||
description: 'list active extensions',
|
||||
action: async (context: CommandContext): Promise<void> => {
|
||||
const activeExtensions = context.services.config?.getActiveExtensions();
|
||||
const activeExtensions = context.services.config
|
||||
?.getExtensions()
|
||||
.filter((ext) => ext.isActive);
|
||||
if (!activeExtensions || activeExtensions.length === 0) {
|
||||
context.ui.addItem(
|
||||
{
|
||||
|
||||
@@ -63,6 +63,7 @@ describe('mcpCommand', () => {
|
||||
let mockConfig: {
|
||||
getToolRegistry: ReturnType<typeof vi.fn>;
|
||||
getMcpServers: ReturnType<typeof vi.fn>;
|
||||
getBlockedMcpServers: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -83,6 +84,7 @@ describe('mcpCommand', () => {
|
||||
getAllTools: vi.fn().mockReturnValue([]),
|
||||
}),
|
||||
getMcpServers: vi.fn().mockReturnValue({}),
|
||||
getBlockedMcpServers: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
@@ -419,6 +421,61 @@ describe('mcpCommand', () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should display the extension name for servers from extensions', async () => {
|
||||
const mockMcpServers = {
|
||||
server1: { command: 'cmd1', extensionName: 'my-extension' },
|
||||
};
|
||||
mockConfig.getMcpServers = vi.fn().mockReturnValue(mockMcpServers);
|
||||
|
||||
const result = await mcpCommand.action!(mockContext, '');
|
||||
|
||||
expect(isMessageAction(result)).toBe(true);
|
||||
if (isMessageAction(result)) {
|
||||
const message = result.content;
|
||||
expect(message).toContain('server1 (from my-extension)');
|
||||
}
|
||||
});
|
||||
|
||||
it('should display blocked MCP servers', async () => {
|
||||
mockConfig.getMcpServers = vi.fn().mockReturnValue({});
|
||||
const blockedServers = [
|
||||
{ name: 'blocked-server', extensionName: 'my-extension' },
|
||||
];
|
||||
mockConfig.getBlockedMcpServers = vi.fn().mockReturnValue(blockedServers);
|
||||
|
||||
const result = await mcpCommand.action!(mockContext, '');
|
||||
|
||||
expect(isMessageAction(result)).toBe(true);
|
||||
if (isMessageAction(result)) {
|
||||
const message = result.content;
|
||||
expect(message).toContain(
|
||||
'🔴 \u001b[1mblocked-server (from my-extension)\u001b[0m - Blocked',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should display both active and blocked servers correctly', async () => {
|
||||
const mockMcpServers = {
|
||||
server1: { command: 'cmd1', extensionName: 'my-extension' },
|
||||
};
|
||||
mockConfig.getMcpServers = vi.fn().mockReturnValue(mockMcpServers);
|
||||
const blockedServers = [
|
||||
{ name: 'blocked-server', extensionName: 'another-extension' },
|
||||
];
|
||||
mockConfig.getBlockedMcpServers = vi.fn().mockReturnValue(blockedServers);
|
||||
|
||||
const result = await mcpCommand.action!(mockContext, '');
|
||||
|
||||
expect(isMessageAction(result)).toBe(true);
|
||||
if (isMessageAction(result)) {
|
||||
const message = result.content;
|
||||
expect(message).toContain('server1 (from my-extension)');
|
||||
expect(message).toContain(
|
||||
'🔴 \u001b[1mblocked-server (from another-extension)\u001b[0m - Blocked',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('schema functionality', () => {
|
||||
|
||||
@@ -49,8 +49,9 @@ const getMcpStatus = async (
|
||||
|
||||
const mcpServers = config.getMcpServers() || {};
|
||||
const serverNames = Object.keys(mcpServers);
|
||||
const blockedMcpServers = config.getBlockedMcpServers() || [];
|
||||
|
||||
if (serverNames.length === 0) {
|
||||
if (serverNames.length === 0 && blockedMcpServers.length === 0) {
|
||||
const docsUrl = 'https://goo.gle/gemini-cli-docs-mcp';
|
||||
if (process.env.SANDBOX && process.env.SANDBOX !== 'sandbox-exec') {
|
||||
return {
|
||||
@@ -118,9 +119,13 @@ const getMcpStatus = async (
|
||||
|
||||
// Get server description if available
|
||||
const server = mcpServers[serverName];
|
||||
let serverDisplayName = serverName;
|
||||
if (server.extensionName) {
|
||||
serverDisplayName += ` (from ${server.extensionName})`;
|
||||
}
|
||||
|
||||
// Format server header with bold formatting and status
|
||||
message += `${statusIndicator} \u001b[1m${serverName}\u001b[0m - ${statusText}`;
|
||||
message += `${statusIndicator} \u001b[1m${serverDisplayName}\u001b[0m - ${statusText}`;
|
||||
|
||||
// Add tool count with conditional messaging
|
||||
if (status === MCPServerStatus.CONNECTED) {
|
||||
@@ -192,6 +197,14 @@ const getMcpStatus = async (
|
||||
message += '\n';
|
||||
}
|
||||
|
||||
for (const server of blockedMcpServers) {
|
||||
let serverDisplayName = server.name;
|
||||
if (server.extensionName) {
|
||||
serverDisplayName += ` (from ${server.extensionName})`;
|
||||
}
|
||||
message += `🔴 \u001b[1m${serverDisplayName}\u001b[0m - Blocked\n\n`;
|
||||
}
|
||||
|
||||
// Add helpful tips when no arguments are provided
|
||||
if (showTips) {
|
||||
message += '\n';
|
||||
|
||||
@@ -13,6 +13,7 @@ interface ContextSummaryDisplayProps {
|
||||
geminiMdFileCount: number;
|
||||
contextFileNames: string[];
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
blockedMcpServers?: Array<{ name: string; extensionName: string }>;
|
||||
showToolDescriptions?: boolean;
|
||||
}
|
||||
|
||||
@@ -20,11 +21,17 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
||||
geminiMdFileCount,
|
||||
contextFileNames,
|
||||
mcpServers,
|
||||
blockedMcpServers,
|
||||
showToolDescriptions,
|
||||
}) => {
|
||||
const mcpServerCount = Object.keys(mcpServers || {}).length;
|
||||
const blockedMcpServerCount = blockedMcpServers?.length || 0;
|
||||
|
||||
if (geminiMdFileCount === 0 && mcpServerCount === 0) {
|
||||
if (
|
||||
geminiMdFileCount === 0 &&
|
||||
mcpServerCount === 0 &&
|
||||
blockedMcpServerCount === 0
|
||||
) {
|
||||
return <Text> </Text>; // Render an empty space to reserve height
|
||||
}
|
||||
|
||||
@@ -39,10 +46,27 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
||||
}`;
|
||||
})();
|
||||
|
||||
const mcpText =
|
||||
mcpServerCount > 0
|
||||
? `${mcpServerCount} MCP server${mcpServerCount > 1 ? 's' : ''}`
|
||||
: '';
|
||||
const mcpText = (() => {
|
||||
if (mcpServerCount === 0 && blockedMcpServerCount === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
if (mcpServerCount > 0) {
|
||||
parts.push(
|
||||
`${mcpServerCount} MCP server${mcpServerCount > 1 ? 's' : ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (blockedMcpServerCount > 0) {
|
||||
let blockedText = `${blockedMcpServerCount} blocked`;
|
||||
if (mcpServerCount === 0) {
|
||||
blockedText += ` MCP server${blockedMcpServerCount > 1 ? 's' : ''}`;
|
||||
}
|
||||
parts.push(blockedText);
|
||||
}
|
||||
return parts.join(', ');
|
||||
})();
|
||||
|
||||
let summaryText = 'Using ';
|
||||
if (geminiMdText) {
|
||||
|
||||
Reference in New Issue
Block a user