fix(core): reduce intrusive MCP errors and deduplicate diagnostics (#20232)

This commit is contained in:
Spencer
2026-02-27 15:04:36 -05:00
committed by GitHub
parent 6a0f4d3bdd
commit 20d884da2f
20 changed files with 626 additions and 191 deletions

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mcpCommand } from './mcpCommand.js';
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
import {
@@ -77,6 +77,8 @@ describe('mcpCommand', () => {
getGeminiClient: ReturnType<typeof vi.fn>;
getMcpClientManager: ReturnType<typeof vi.fn>;
getResourceRegistry: ReturnType<typeof vi.fn>;
setUserInteractedWithMcp: ReturnType<typeof vi.fn>;
getLastMcpError: ReturnType<typeof vi.fn>;
};
beforeEach(() => {
@@ -104,12 +106,15 @@ describe('mcpCommand', () => {
}),
getGeminiClient: vi.fn(),
getMcpClientManager: vi.fn().mockImplementation(() => ({
getBlockedMcpServers: vi.fn(),
getMcpServers: vi.fn(),
getBlockedMcpServers: vi.fn().mockReturnValue([]),
getMcpServers: vi.fn().mockReturnValue({}),
getLastError: vi.fn().mockReturnValue(undefined),
})),
getResourceRegistry: vi.fn().mockReturnValue({
getAllResources: vi.fn().mockReturnValue([]),
}),
setUserInteractedWithMcp: vi.fn(),
getLastMcpError: vi.fn().mockReturnValue(undefined),
};
mockContext = createMockCommandContext({
@@ -119,6 +124,10 @@ describe('mcpCommand', () => {
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('basic functionality', () => {
it('should show an error if config is not available', async () => {
const contextWithoutConfig = createMockCommandContext({
@@ -161,6 +170,7 @@ describe('mcpCommand', () => {
mockConfig.getMcpClientManager = vi.fn().mockReturnValue({
getMcpServers: vi.fn().mockReturnValue(mockMcpServers),
getBlockedMcpServers: vi.fn().mockReturnValue([]),
getLastError: vi.fn().mockReturnValue(undefined),
});
});

View File

@@ -52,6 +52,8 @@ const authCommand: SlashCommand = {
};
}
config.setUserInteractedWithMcp();
const mcpServers = config.getMcpClientManager()?.getMcpServers() ?? {};
if (!serverName) {
@@ -184,6 +186,8 @@ const listAction = async (
};
}
config.setUserInteractedWithMcp();
const toolRegistry = config.getToolRegistry();
if (!toolRegistry) {
return {
@@ -250,6 +254,13 @@ const listAction = async (
enablementState[serverName] =
await enablementManager.getDisplayState(serverName);
}
const errors: Record<string, string> = {};
for (const serverName of serverNames) {
const error = config.getMcpClientManager()?.getLastError(serverName);
if (error) {
errors[serverName] = error;
}
}
const mcpStatusItem: HistoryItemMcpStatus = {
type: MessageType.MCP_STATUS,
@@ -274,16 +285,19 @@ const listAction = async (
})),
authStatus,
enablementState,
blockedServers: blockedMcpServers,
errors,
blockedServers: blockedMcpServers.map((s) => ({
name: s.name,
extensionName: s.extensionName,
})),
discoveryInProgress,
connectingServers,
showDescriptions,
showSchema,
showDescriptions: Boolean(showDescriptions),
showSchema: Boolean(showSchema),
};
context.ui.addItem(mcpStatusItem);
};
const listCommand: SlashCommand = {
name: 'list',
altNames: ['ls', 'nodesc', 'nodescription'],
@@ -372,6 +386,8 @@ async function handleEnableDisable(
};
}
config.setUserInteractedWithMcp();
const parts = args.trim().split(/\s+/);
const isSession = parts.includes('--session');
const serverName = parts.filter((p) => p !== '--session')[0];

View File

@@ -47,6 +47,7 @@ describe('McpStatus', () => {
isPersistentDisabled: false,
},
},
errors: {},
discoveryInProgress: false,
connectingServers: [],
showDescriptions: true,
@@ -208,6 +209,18 @@ describe('McpStatus', () => {
unmount();
});
it('renders correctly with a server error', async () => {
const { lastFrame, unmount, waitUntilReady } = render(
<McpStatus
{...baseProps}
errors={{ 'server-1': 'Failed to connect to server' }}
/>,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('truncates resources when exceeding limit', async () => {
const manyResources = Array.from({ length: 25 }, (_, i) => ({
serverName: 'server-1',

View File

@@ -26,6 +26,7 @@ interface McpStatusProps {
serverStatus: (serverName: string) => MCPServerStatus;
authStatus: HistoryItemMcpStatus['authStatus'];
enablementState: HistoryItemMcpStatus['enablementState'];
errors: Record<string, string>;
discoveryInProgress: boolean;
connectingServers: string[];
showDescriptions: boolean;
@@ -41,6 +42,7 @@ export const McpStatus: React.FC<McpStatusProps> = ({
serverStatus,
authStatus,
enablementState,
errors,
discoveryInProgress,
connectingServers,
showDescriptions,
@@ -195,6 +197,14 @@ export const McpStatus: React.FC<McpStatusProps> = ({
<Text> ({toolCount} tools cached)</Text>
)}
{errors[serverName] && (
<Box marginLeft={2}>
<Text color={theme.status.error}>
Error: {errors[serverName]}
</Text>
</Box>
)}
{showDescriptions && server?.description && (
<Text color={theme.text.secondary}>
{server.description.trim()}

View File

@@ -60,6 +60,18 @@ A test server
"
`;
exports[`McpStatus > renders correctly with a server error 1`] = `
"Configured MCP servers:
🟢 server-1 - Ready (1 tool)
Error: Failed to connect to server
A test server
Tools:
- tool-1
A test tool
"
`;
exports[`McpStatus > renders correctly with authenticated OAuth status 1`] = `
"Configured MCP servers:

View File

@@ -340,6 +340,7 @@ export type HistoryItemMcpStatus = HistoryItemBase & {
isPersistentDisabled: boolean;
}
>;
errors: Record<string, string>;
blockedServers: Array<{ name: string; extensionName: string }>;
discoveryInProgress: boolean;
connectingServers: string[];