Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 38 additions & 0 deletions packages/core/src/tools/mcp-tool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,44 @@ describe('DiscoveredMCPTool', () => {
},
);

it('should return a structured error if MCP tool reports a top-level isError (spec compliant)', async () => {
const tool = new DiscoveredMCPTool(
mockCallableToolInstance,
serverName,
serverToolName,
baseDescription,
inputSchema,
);
const params = { param: 'isErrorTopLevelCase' };
const functionCall = {
name: serverToolName,
args: params,
};

// Spec compliant error response: { isError: true } at the top level of content (or response object in this mapping)
const errorResponse = { isError: true };
const mockMcpToolResponseParts: Part[] = [
{
functionResponse: {
name: serverToolName,
response: errorResponse,
},
},
];
mockCallTool.mockResolvedValue(mockMcpToolResponseParts);
const expectedErrorMessage = `MCP tool '${serverToolName}' reported tool error for function call: ${safeJsonStringify(
functionCall,
)} with response: ${safeJsonStringify(mockMcpToolResponseParts)}`;
const invocation = tool.build(params);
const result = await invocation.execute(new AbortController().signal);

expect(result.error?.type).toBe(ToolErrorType.MCP_TOOL_ERROR);
expect(result.llmContent).toBe(expectedErrorMessage);
expect(result.returnDisplay).toContain(
`Error: MCP tool '${serverToolName}' reported an error.`,
);
});
Comment on lines +226 to +262
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The new test case for spec-compliant MCP errors only validates the case where isError is a boolean true. To ensure the fix is robust and prevent future regressions, it's important to also test the case where isError is the string 'true', as the implementation correctly handles both. I suggest converting this test to use it.each to cover both scenarios, similar to the existing test for legacy error formats.

    it.each([
      { isErrorValue: true, description: 'true (bool)' },
      { isErrorValue: 'true', description: '"true" (str)' },
    ])(
      'should return a structured error if MCP tool reports a top-level isError: $description (spec compliant)',
      async ({ isErrorValue }) => {
        const tool = new DiscoveredMCPTool(
          mockCallableToolInstance,
          serverName,
          serverToolName,
          baseDescription,
          inputSchema,
        );
        const params = { param: 'isErrorTopLevelCase' };
        const functionCall = {
          name: serverToolName,
          args: params,
        };

        // Spec compliant error response: { isError: true } at the top level of content (or response object in this mapping)
        const errorResponse = { isError: isErrorValue };
        const mockMcpToolResponseParts: Part[] = [
          {
            functionResponse: {
              name: serverToolName,
              response: errorResponse,
            },
          },
        ];
        mockCallTool.mockResolvedValue(mockMcpToolResponseParts);
        const expectedErrorMessage = `MCP tool '${serverToolName}' reported tool error for function call: ${safeJsonStringify(
          functionCall,
        )} with response: ${safeJsonStringify(mockMcpToolResponseParts)}`;
        const invocation = tool.build(params);
        const result = await invocation.execute(new AbortController().signal);

        expect(result.error?.type).toBe(ToolErrorType.MCP_TOOL_ERROR);
        expect(result.llmContent).toBe(expectedErrorMessage);
        expect(result.returnDisplay).toContain(
          `Error: MCP tool '${serverToolName}' reported an error.`,
        );
      },
    );


it.each([
{ isErrorValue: false, description: 'false (bool)' },
{ isErrorValue: 'false', description: '"false" (str)' },
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/tools/mcp-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ class DiscoveredMCPToolInvocation extends BaseToolInvocation<
}

if (response) {
// Check for top-level isError (MCP Spec compliant)
const isErrorTop = (response as { isError?: boolean | string }).isError;
if (isErrorTop === true || isErrorTop === 'true') {
return true;
}

// Legacy check for nested error object (keep for backward compatibility if any tools rely on it)
const error = (response as { error?: McpError })?.error;
const isError = error?.isError;

Expand Down
Loading