@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
JavaScript
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 = [];