Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions packages/core/src/tools/mcp-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/tools/mcp-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
Loading