fix: prevent tools discovery error for prompt-only MCP servers (#10367)

Co-authored-by: Tommaso Sciortino <sciortino@gmail.com>
This commit is contained in:
Vasily Tsybenko
2025-10-02 22:47:58 +03:00
committed by GitHub
parent 63efcb6b66
commit 0c6f9d2898
2 changed files with 90 additions and 0 deletions

View File

@@ -43,6 +43,7 @@ describe('mcp-client', () => {
getStatus: vi.fn(),
registerCapabilities: vi.fn(),
setRequestHandler: vi.fn(),
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
};
vi.mocked(ClientLib.Client).mockReturnValue(
mockedClient as unknown as ClientLib.Client,
@@ -88,6 +89,7 @@ describe('mcp-client', () => {
getStatus: vi.fn(),
registerCapabilities: vi.fn(),
setRequestHandler: vi.fn(),
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
tool: vi.fn(),
};
vi.mocked(ClientLib.Client).mockReturnValue(
@@ -183,6 +185,91 @@ describe('mcp-client', () => {
);
consoleErrorSpy.mockRestore();
});
it('should not discover tools if server does not support them', async () => {
const mockedClient = {
connect: vi.fn(),
discover: vi.fn(),
disconnect: vi.fn(),
getStatus: vi.fn(),
registerCapabilities: vi.fn(),
setRequestHandler: vi.fn(),
getServerCapabilities: vi.fn().mockReturnValue({ prompts: {} }),
request: vi.fn().mockResolvedValue({ prompts: [] }),
};
vi.mocked(ClientLib.Client).mockReturnValue(
mockedClient as unknown as ClientLib.Client,
);
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue(
{} as SdkClientStdioLib.StdioClientTransport,
);
const mockedMcpToTool = vi.mocked(GenAiLib.mcpToTool);
const mockedToolRegistry = {
registerTool: vi.fn(),
} as unknown as ToolRegistry;
const client = new McpClient(
'test-server',
{
command: 'test-command',
},
mockedToolRegistry,
{} as PromptRegistry,
{} as WorkspaceContext,
false,
);
await client.connect();
await expect(client.discover({} as Config)).rejects.toThrow(
'No prompts or tools found on the server.',
);
expect(mockedMcpToTool).not.toHaveBeenCalled();
});
it('should discover tools if server supports them', async () => {
const mockedClient = {
connect: vi.fn(),
discover: vi.fn(),
disconnect: vi.fn(),
getStatus: vi.fn(),
registerCapabilities: vi.fn(),
setRequestHandler: vi.fn(),
getServerCapabilities: vi.fn().mockReturnValue({ tools: {} }),
request: vi.fn().mockResolvedValue({ prompts: [] }),
};
vi.mocked(ClientLib.Client).mockReturnValue(
mockedClient as unknown as ClientLib.Client,
);
vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue(
{} as SdkClientStdioLib.StdioClientTransport,
);
const mockedMcpToTool = vi.mocked(GenAiLib.mcpToTool).mockReturnValue({
tool: () =>
Promise.resolve({
functionDeclarations: [
{
name: 'testTool',
description: 'A test tool',
},
],
}),
} as unknown as GenAiLib.CallableTool);
const mockedToolRegistry = {
registerTool: vi.fn(),
} as unknown as ToolRegistry;
const client = new McpClient(
'test-server',
{
command: 'test-command',
},
mockedToolRegistry,
{} as PromptRegistry,
{} as WorkspaceContext,
false,
);
await client.connect();
await client.discover({} as Config);
expect(mockedMcpToTool).toHaveBeenCalledOnce();
expect(mockedToolRegistry.registerTool).toHaveBeenCalledOnce();
});
});
describe('appendMcpServerCommand', () => {
it('should do nothing if no MCP servers or command are configured', () => {

View File

@@ -584,6 +584,9 @@ export async function discoverTools(
cliConfig: Config,
): Promise<DiscoveredMCPTool[]> {
try {
// Only request tools if the server supports them.
if (mcpClient.getServerCapabilities()?.tools == null) return [];
const mcpCallableTool = mcpToTool(mcpClient, {
timeout: mcpServerConfig.timeout ?? MCP_DEFAULT_TIMEOUT_MSEC,
});