Enhance content compatibility checks for tool results with multiple blocks

Co-authored-by: me <me@kentcdodds.com>
This commit is contained in:
Cursor Agent 2025-07-10 12:45:47 +00:00
commit cbb3ebcd1a
3 changed files with 313 additions and 33 deletions

View file

@ -19,46 +19,44 @@ const checkContentCompatibility = (
[key: string]: unknown;
}>,
): { isCompatible: boolean; message: string } => {
if (
unstructuredContent.length !== 1 ||
unstructuredContent[0].type !== "text"
) {
// Look for at least one text content block that matches the structured content
const textBlocks = unstructuredContent.filter(block => block.type === "text");
if (textBlocks.length === 0) {
return {
isCompatible: false,
message: "Unstructured content is not a single text block",
message: "No text content blocks found to match structured content",
};
}
const textContent = unstructuredContent[0].text;
if (!textContent) {
return {
isCompatible: false,
message: "Text content is empty",
};
}
try {
const parsedContent = JSON.parse(textContent);
const isEqual =
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
if (isEqual) {
return {
isCompatible: true,
message: "Unstructured content matches structured content",
};
} else {
return {
isCompatible: false,
message: "Parsed JSON does not match structured content",
};
// Check if any text block contains JSON that matches the structured content
for (const textBlock of textBlocks) {
const textContent = textBlock.text;
if (!textContent) {
continue;
}
try {
const parsedContent = JSON.parse(textContent);
const isEqual =
JSON.stringify(parsedContent) === JSON.stringify(structuredContent);
if (isEqual) {
return {
isCompatible: true,
message: `Found matching JSON content (${textBlocks.length > 1 ? 'among multiple text blocks' : 'in single text block'})${unstructuredContent.length > textBlocks.length ? ' with additional content blocks' : ''}`,
};
}
} catch {
// Continue to next text block if this one doesn't parse as JSON
continue;
}
} catch {
return {
isCompatible: false,
message: "Unstructured content is not valid JSON",
};
}
return {
isCompatible: false,
message: "No text content block contains JSON matching structured content",
};
};
const ToolResults = ({ toolResult, selectedTool }: ToolResultsProps) => {

View file

@ -0,0 +1,185 @@
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { describe, it, beforeEach } from "@jest/globals";
import ToolResults from "../ToolResults";
import { Tool } from "@modelcontextprotocol/sdk/types.js";
import { cacheToolOutputSchemas } from "@/utils/schemaUtils";
describe("ToolResults", () => {
const mockTool: Tool = {
name: "testTool",
description: "Test tool",
inputSchema: {
type: "object",
properties: {},
},
outputSchema: {
type: "object",
properties: {
result: { type: "string" },
},
required: ["result"],
},
};
beforeEach(() => {
cacheToolOutputSchemas([mockTool]);
});
describe("Content Compatibility Validation", () => {
it("should accept single text block with matching JSON", () => {
const toolResult = {
content: [{ type: "text", text: '{"result": "success"}' }],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Found matching JSON content.*in single text block/)).toBeInTheDocument();
});
it("should accept multiple text blocks with one matching JSON", () => {
const toolResult = {
content: [
{ type: "text", text: "Processing..." },
{ type: "text", text: '{"result": "success"}' },
{ type: "text", text: "Done!" },
],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Found matching JSON content.*among multiple text blocks/)).toBeInTheDocument();
});
it("should accept mixed content types with matching JSON", () => {
const toolResult = {
content: [
{ type: "text", text: "Result:" },
{ type: "text", text: '{"result": "success"}' },
{ type: "image", data: "base64data", mimeType: "image/png" },
{ type: "resource", resource: { uri: "file://test.txt" } },
],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Found matching JSON content.*with additional content blocks/)).toBeInTheDocument();
});
it("should reject when no text blocks are present", () => {
const toolResult = {
content: [
{ type: "image", data: "base64data", mimeType: "image/png" },
],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/No text content blocks found to match structured content/)).toBeInTheDocument();
});
it("should reject when no text blocks contain matching JSON", () => {
const toolResult = {
content: [
{ type: "text", text: "Some text" },
{ type: "text", text: '{"different": "data"}' },
],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/No text content block contains JSON matching structured content/)).toBeInTheDocument();
});
it("should reject when text blocks contain invalid JSON", () => {
const toolResult = {
content: [
{ type: "text", text: "Not JSON" },
{ type: "text", text: '{"invalid": json}' },
],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/No text content block contains JSON matching structured content/)).toBeInTheDocument();
});
it("should handle empty text blocks gracefully", () => {
const toolResult = {
content: [
{ type: "text", text: "" },
{ type: "text", text: '{"result": "success"}' },
],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Found matching JSON content.*among multiple text blocks/)).toBeInTheDocument();
});
it("should not show compatibility check when tool has no output schema", () => {
const toolWithoutSchema: Tool = {
name: "noSchemaTool",
description: "Tool without schema",
inputSchema: {
type: "object",
properties: {},
},
};
const toolResult = {
content: [{ type: "text", text: '{"any": "data"}' }],
structuredContent: { any: "data" },
};
render(<ToolResults toolResult={toolResult} selectedTool={toolWithoutSchema} />);
// Should not show any compatibility messages
expect(screen.queryByText(/Found matching JSON content/)).not.toBeInTheDocument();
expect(screen.queryByText(/No text content blocks found/)).not.toBeInTheDocument();
expect(screen.queryByText(/No text content block contains JSON/)).not.toBeInTheDocument();
});
});
describe("Structured Content Validation", () => {
it("should show validation success for valid structured content", () => {
const toolResult = {
content: [],
structuredContent: { result: "success" },
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Valid according to output schema/)).toBeInTheDocument();
});
it("should show validation error for invalid structured content", () => {
const toolResult = {
content: [],
structuredContent: { result: 123 }, // Should be string
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Validation Error:/)).toBeInTheDocument();
});
it("should show error when structured content is missing for tool with output schema", () => {
const toolResult = {
content: [{ type: "text", text: "Some result" }],
// No structuredContent
};
render(<ToolResults toolResult={toolResult} selectedTool={mockTool} />);
expect(screen.getByText(/Tool has an output schema but did not return structured content/)).toBeInTheDocument();
});
});
});

View file

@ -357,7 +357,104 @@ describe("ToolsTab", () => {
// Should show compatibility result
expect(
screen.getByText(
/matches structured content|not a single text block|not valid JSON|does not match/,
/Found matching JSON content/,
),
).toBeInTheDocument();
});
it("should accept multiple content blocks with structured output", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const multipleBlocksResult = {
content: [
{ type: "text", text: "Here is the weather data:" },
{ type: "text", text: '{"temperature": 25}' },
{ type: "text", text: "Have a nice day!" },
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
selectedTool: toolWithOutputSchema,
toolResult: multipleBlocksResult,
});
// Should show compatible result with multiple blocks
expect(
screen.getByText(
/Found matching JSON content.*among multiple text blocks/,
),
).toBeInTheDocument();
});
it("should accept mixed content types with structured output", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const mixedContentResult = {
content: [
{ type: "text", text: "Weather report:" },
{ type: "text", text: '{"temperature": 25}' },
{ type: "image", data: "base64data", mimeType: "image/png" },
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
selectedTool: toolWithOutputSchema,
toolResult: mixedContentResult,
});
// Should show compatible result with additional content blocks
expect(
screen.getByText(
/Found matching JSON content.*with additional content blocks/,
),
).toBeInTheDocument();
});
it("should reject when no text blocks match structured content", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const noMatchResult = {
content: [
{ type: "text", text: "Some text" },
{ type: "text", text: '{"humidity": 60}' }, // Different structure
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
selectedTool: toolWithOutputSchema,
toolResult: noMatchResult,
});
// Should show incompatible result
expect(
screen.getByText(
/No text content block contains JSON matching structured content/,
),
).toBeInTheDocument();
});
it("should reject when no text blocks are present", () => {
cacheToolOutputSchemas([toolWithOutputSchema]);
const noTextBlocksResult = {
content: [
{ type: "image", data: "base64data", mimeType: "image/png" },
],
structuredContent: { temperature: 25 },
};
renderToolsTab({
selectedTool: toolWithOutputSchema,
toolResult: noTextBlocksResult,
});
// Should show incompatible result
expect(
screen.getByText(
/No text content blocks found to match structured content/,
),
).toBeInTheDocument();
});
@ -376,7 +473,7 @@ describe("ToolsTab", () => {
// Should not show any compatibility messages
expect(
screen.queryByText(
/matches structured content|not a single text block|not valid JSON|does not match/,
/Found matching JSON content|No text content blocks found|No text content block contains JSON/,
),
).not.toBeInTheDocument();
});