diff --git a/packages/core/src/tools/mcp-client.test.ts b/packages/core/src/tools/mcp-client.test.ts index a8a94484cf9..85e6ff13714 100644 --- a/packages/core/src/tools/mcp-client.test.ts +++ b/packages/core/src/tools/mcp-client.test.ts @@ -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', () => { diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index e6ece88e45f..ca25bde4533 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -584,6 +584,9 @@ export async function discoverTools( cliConfig: Config, ): Promise { 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, });