import JsonView from "./JsonView"; import ResourceLinkView from "./ResourceLinkView"; import { CallToolResultSchema, CompatibilityCallToolResult, Tool, } from "@modelcontextprotocol/sdk/types.js"; import { validateToolOutput, hasOutputSchema } from "@/utils/schemaUtils"; interface ToolResultsProps { toolResult: CompatibilityCallToolResult | null; selectedTool: Tool | null; resourceContent: Record; onReadResource?: (uri: string) => void; } const checkContentCompatibility = ( structuredContent: unknown, unstructuredContent: Array<{ type: string; text?: string; [key: string]: unknown; }>, ): { isCompatible: boolean; message: string } => { // 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: "No text blocks to 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: `Structured content matches text block${textBlocks.length > 1 ? " (multiple blocks)" : ""}${unstructuredContent.length > textBlocks.length ? " + other content" : ""}`, }; } } catch { // Continue to next text block if this one doesn't parse as JSON continue; } } return { isCompatible: false, message: "No text block matches structured content", }; }; const ToolResults = ({ toolResult, selectedTool, resourceContent, onReadResource, }: ToolResultsProps) => { if (!toolResult) return null; if ("content" in toolResult) { const parsedResult = CallToolResultSchema.safeParse(toolResult); if (!parsedResult.success) { return ( <>

Invalid Tool Result:

Errors:

{parsedResult.error.errors.map((error, idx) => ( ))} ); } const structuredResult = parsedResult.data; const isError = structuredResult.isError ?? false; let validationResult = null; const toolHasOutputSchema = selectedTool && hasOutputSchema(selectedTool.name); if (toolHasOutputSchema) { if (!structuredResult.structuredContent && !isError) { validationResult = { isValid: false, error: "Tool has an output schema but did not return structured content", }; } else if (structuredResult.structuredContent) { validationResult = validateToolOutput( selectedTool.name, structuredResult.structuredContent, ); } } let compatibilityResult = null; if ( structuredResult.structuredContent && structuredResult.content.length > 0 && selectedTool && hasOutputSchema(selectedTool.name) ) { compatibilityResult = checkContentCompatibility( structuredResult.structuredContent, structuredResult.content, ); } return ( <>

Tool Result:{" "} {isError ? ( Error ) : ( Success )}

{structuredResult.structuredContent && (
Structured Content:
{validationResult && (
{validationResult.isValid ? ( "✓ Valid according to output schema" ) : ( <>✗ Validation Error: {validationResult.error} )}
)}
)} {!structuredResult.structuredContent && validationResult && !validationResult.isValid && (
✗ Validation Error: {validationResult.error}
)} {structuredResult.content.length > 0 && (
{structuredResult.structuredContent && ( <>
Unstructured Content:
{compatibilityResult && (
{compatibilityResult.isCompatible ? "✓" : "⚠"}{" "} {compatibilityResult.message}
)} )} {structuredResult.content.map((item, index) => (
{item.type === "text" && ( )} {item.type === "image" && ( Tool result image )} {item.type === "resource" && (item.resource?.mimeType?.startsWith("audio/") ? ( ) : ( ))} {item.type === "resource_link" && ( )}
))}
)} ); } else if ("toolResult" in toolResult) { return ( <>

Tool Result (Legacy):

); } return null; }; export default ToolResults;