fix: Do not throw on tool errors, instead return error message (#17558)

This commit is contained in:
Elias Meire
2025-07-23 12:54:31 +02:00
committed by GitHub
parent 917eabe2f7
commit f11ec538dc
3 changed files with 94 additions and 16 deletions

View File

@@ -2,6 +2,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { mock } from 'jest-mock-extended';
import {
NodeConnectionTypes,
NodeOperationError,
type ILoadOptionsFunctions,
type INode,
@@ -284,5 +285,78 @@ describe('McpClientTool', () => {
headers: { Accept: 'text/event-stream', Authorization: 'Bearer my-token' },
});
});
it('should successfully execute a tool', async () => {
jest.spyOn(Client.prototype, 'connect').mockResolvedValue();
jest.spyOn(Client.prototype, 'callTool').mockResolvedValue({ content: 'Sunny' });
jest.spyOn(Client.prototype, 'listTools').mockResolvedValue({
tools: [
{
name: 'Weather Tool',
description: 'Gets the current weather',
inputSchema: { type: 'object', properties: { location: { type: 'string' } } },
},
],
});
const supplyDataResult = await new McpClientTool().supplyData.call(
mock<ISupplyDataFunctions>({
getNode: jest.fn(() =>
mock<INode>({
typeVersion: 1,
}),
),
logger: { debug: jest.fn(), error: jest.fn() },
addInputData: jest.fn(() => ({ index: 0 })),
}),
0,
);
expect(supplyDataResult.closeFunction).toBeInstanceOf(Function);
expect(supplyDataResult.response).toBeInstanceOf(McpToolkit);
const tools = (supplyDataResult.response as McpToolkit).getTools();
const toolResult = await tools[0].invoke({ location: 'Berlin' });
expect(toolResult).toEqual('Sunny');
});
it('should handle tool errors', async () => {
jest.spyOn(Client.prototype, 'connect').mockResolvedValue();
jest
.spyOn(Client.prototype, 'callTool')
.mockResolvedValue({ isError: true, content: [{ text: 'Weather unknown at location' }] });
jest.spyOn(Client.prototype, 'listTools').mockResolvedValue({
tools: [
{
name: 'Weather Tool',
description: 'Gets the current weather',
inputSchema: { type: 'object', properties: { location: { type: 'string' } } },
},
],
});
const supplyDataFunctions = mock<ISupplyDataFunctions>({
getNode: jest.fn(() =>
mock<INode>({
typeVersion: 1,
}),
),
logger: { debug: jest.fn(), error: jest.fn() },
addInputData: jest.fn(() => ({ index: 0 })),
});
const supplyDataResult = await new McpClientTool().supplyData.call(supplyDataFunctions, 0);
expect(supplyDataResult.closeFunction).toBeInstanceOf(Function);
expect(supplyDataResult.response).toBeInstanceOf(McpToolkit);
const tools = (supplyDataResult.response as McpToolkit).getTools();
const toolResult = await tools[0].invoke({ location: 'Berlin' });
expect(toolResult).toEqual('Weather unknown at location');
expect(supplyDataFunctions.addOutputData).toHaveBeenCalledWith(
NodeConnectionTypes.AiTool,
0,
new NodeOperationError(supplyDataFunctions.getNode(), 'Weather unknown at location'),
);
});
});
});