UNPKG

@emmahyde/thinking-patterns

Version:

MCP server combining systematic thinking, mental models, debugging approaches, and stochastic algorithms for comprehensive cognitive pattern support

289 lines (288 loc) 9.03 kB
import { z } from 'zod'; /** * Convert Zod schema to JSON schema for MCP tool definitions */ function zodToJsonSchema(zodSchema) { // Basic conversion for common Zod types // This is a simplified implementation - could be enhanced with a library like zod-to-json-schema if (zodSchema instanceof z.ZodObject) { const shape = zodSchema._def.shape(); const properties = {}; const required = []; for (const [key, fieldSchema] of Object.entries(shape)) { const field = fieldSchema; properties[key] = getFieldSchema(field); // Check if field is required (not optional) if (!field.isOptional()) { required.push(key); } } return { type: "object", properties, required, additionalProperties: false }; } // Fallback for non-object schemas return { type: "object", properties: {}, required: [] }; } /** * Get JSON schema for individual Zod field */ function getFieldSchema(field) { // Extract description if available const description = field._def.description; if (field instanceof z.ZodString) { const schema = { type: "string" }; if (description) { schema.description = description; } if (field._def.checks) { for (const check of field._def.checks) { if (check.kind === "min") { schema.minLength = check.value; } if (check.kind === "max") { schema.maxLength = check.value; } } } return schema; } if (field instanceof z.ZodNumber) { const schema = { type: "number" }; if (description) { schema.description = description; } if (field._def.checks) { for (const check of field._def.checks) { if (check.kind === "min") { schema.minimum = check.value; } if (check.kind === "max") { schema.maximum = check.value; } if (check.kind === "int") { schema.type = "integer"; } } } return schema; } if (field instanceof z.ZodBoolean) { const schema = { type: "boolean" }; if (description) { schema.description = description; } return schema; } if (field instanceof z.ZodArray) { const schema = { type: "array", items: getFieldSchema(field._def.type) }; if (description) { schema.description = description; } return schema; } if (field instanceof z.ZodObject) { const schema = zodToJsonSchema(field); if (description) { schema.description = description; } return schema; } if (field instanceof z.ZodOptional) { const innerSchema = getFieldSchema(field._def.innerType); // Preserve description from the optional wrapper if it exists if (description && !innerSchema.description) { innerSchema.description = description; } return innerSchema; } if (field instanceof z.ZodEnum) { const schema = { type: "string", enum: field._def.values }; if (description) { schema.description = description; } return schema; } if (field instanceof z.ZodRecord) { const schema = { type: "object", additionalProperties: getFieldSchema(field._def.valueType) }; if (description) { schema.description = description; } return schema; } if (field instanceof z.ZodUnion) { // Handle union types by taking the first option as primary schema // This is a simplified approach - could be enhanced to use anyOf/oneOf const options = field._def.options; if (options && options.length > 0) { const primarySchema = getFieldSchema(options[0]); if (description) { primarySchema.description = description; } return primarySchema; } } // Fallback for unknown types const schema = { type: "string" }; if (description) { schema.description = description; } return schema; } /** * Abstract base class for all tool servers * Provides standardized validation, error handling, and response formatting */ export class BaseToolServer { /** * Constructor that accepts a Zod schema for input validation * @param schema - Zod schema for validating input data */ constructor(schema) { this.schema = schema; } /** * Validates input using the provided Zod schema * @param input - Raw input data to validate * @returns Validated and typed input data * @throws Error if validation fails */ validate(input) { try { return this.schema.parse(input); } catch (error) { if (error instanceof z.ZodError) { const errorMessages = error.errors.map(err => `${err.path.join('.')}: ${err.message}`).join(', '); throw new Error(`Validation failed: ${errorMessages}`); } throw error; } } /** * Standardized process method for unified server interface * Default implementation delegates to handle method for backward compatibility * Servers can override this to provide standardized processing interface * @param validInput - Validated input data * @returns Processed output data */ process(validInput) { return this.handle(validInput); } /** * Main entry point that wraps validation, processing, and error handling * Provides standardized {content, isError} envelope response * @param rawInput - Raw input data from MCP request * @returns Standardized MCP response */ run(rawInput) { try { // Validate input using schema const validatedInput = this.validate(rawInput); // Process with concrete implementation const result = this.handle(validatedInput); // Format successful response return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error) { // Format error response return { content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), status: 'failed', timestamp: new Date().toISOString() }, null, 2) }], isError: true }; } } /** * Optional method for servers that need custom response formatting * @param result - Result from handle method * @returns Formatted response content */ formatResponse(result) { return [{ type: "text", text: JSON.stringify(result, null, 2) }]; } /** * Optional method for servers that need custom error formatting * @param error - Error that occurred during processing * @returns Formatted error response content */ formatError(error) { return [{ type: "text", text: JSON.stringify({ error: error.message, status: 'failed', timestamp: new Date().toISOString() }, null, 2) }]; } } /** * Tool registry for managing all available tools */ export class ToolRegistry { /** * Register a new tool * @param entry - Tool registry entry */ static register(entry) { this.tools.push(entry); } /** * Find a tool by name * @param name - Tool name * @returns Tool registry entry or undefined */ static findTool(name) { return this.tools.find(tool => tool.name === name); } /** * Get all registered tools * @returns Array of all tool registry entries */ static getAllTools() { return [...this.tools]; } /** * Get tool names for MCP ListTools response * @returns Array of tool definitions for MCP */ static getToolDefinitions() { return this.tools.map(tool => ({ name: tool.name, description: tool.description || `Tool for ${tool.name} operations`, inputSchema: zodToJsonSchema(tool.schema) })); } } ToolRegistry.tools = [];