feat: Add structured output support to browser UI

This commit adds comprehensive support for structured output validation in tool calls,
following the MCP specification for tools with output schemas.

UI Changes:
1. Output Schema Display:
   - Added collapsible output schema section in ToolsTab
   - Shows output schemas after input fields, before the Run Tool button
   - Default view shows 8 lines with scrolling, expandable to full view
   - Expand/Collapse button with chevron icons for better UX

2. Structured Content Display:
   - New "Structured Content" section when tools return structuredContent
   - Shows structured data in a formatted JSON view
   - Validation status indicator (green checkmark for valid, red X for errors)
   - Detailed validation error messages when content doesn't match schema

3. Unstructured Content Labeling:
   - Added "Unstructured Content" heading when both structured and unstructured content exist
   - Only shows label when structured content is also present
   - Maintains clean UI when only unstructured content exists

4. Compatibility Checking:
   - Checks if unstructured content matches structured content (when output schema exists)
   - Shows compatibility status with blue (compatible) or yellow (incompatible) indicators
   - Detailed messages explain why content doesn't match
   - Only runs compatibility check for tools with output schemas

5. Validation Error Handling:
   - Shows error when tool with output schema doesn't return structured content
   - Clear error messages for schema validation failures
   - Maintains proper error state display

Technical Implementation:
- Added schema validation utilities using Ajv (same as SDK)
- Caches compiled validators for performance
- Validates on tool result display, not during the call
- Follows SDK's Client.callTool validation pattern

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Basil Hosmer 2025-05-27 16:39:39 -04:00
commit de84c472ce
No known key found for this signature in database

View file

@ -1,4 +1,78 @@
import type { JsonValue, JsonSchemaType, JsonObject } from "./jsonUtils";
import Ajv from "ajv";
import type { ValidateFunction } from "ajv";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
// Create a single Ajv instance following the SDK pattern
const ajv = new Ajv();
// Cache for compiled validators
const toolOutputValidators = new Map<string, ValidateFunction>();
/**
* Compiles and caches output schema validators for a list of tools
* Following the same pattern as SDK's Client.cacheToolOutputSchemas
* @param tools Array of tools that may have output schemas
*/
export function cacheToolOutputSchemas(tools: Tool[]): void {
toolOutputValidators.clear();
for (const tool of tools) {
if (tool.outputSchema) {
try {
const validator = ajv.compile(tool.outputSchema);
toolOutputValidators.set(tool.name, validator);
} catch (error) {
console.warn(`Failed to compile output schema for tool ${tool.name}:`, error);
}
}
}
}
/**
* Gets the cached output schema validator for a tool
* Following the same pattern as SDK's Client.getToolOutputValidator
* @param toolName Name of the tool
* @returns The compiled validator function, or undefined if not found
*/
export function getToolOutputValidator(toolName: string): ValidateFunction | undefined {
return toolOutputValidators.get(toolName);
}
/**
* Validates structured content against a tool's output schema
* Returns validation result with detailed error messages
* @param toolName Name of the tool
* @param structuredContent The structured content to validate
* @returns An object with isValid boolean and optional error message
*/
export function validateToolOutput(
toolName: string,
structuredContent: unknown
): { isValid: boolean; error?: string } {
const validator = getToolOutputValidator(toolName);
if (!validator) {
return { isValid: true }; // No validator means no schema to validate against
}
const isValid = validator(structuredContent);
if (!isValid) {
return {
isValid: false,
error: ajv.errorsText(validator.errors),
};
}
return { isValid: true };
}
/**
* Checks if a tool has an output schema
* @param toolName Name of the tool
* @returns true if the tool has an output schema
*/
export function hasOutputSchema(toolName: string): boolean {
return toolOutputValidators.has(toolName);
}
/**
* Generates a default value based on a JSON schema type