UNPKG

marketing-post-generator-mcp

Version:

A powerful MCP server for AI-powered marketing blog post generation with Claude integration

398 lines 15.6 kB
/** * Validator for ensuring MCP specification compliance */ export class RegistryValidator { TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/; PROMPT_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/; MAX_NAME_LENGTH = 100; MAX_DESCRIPTION_LENGTH = 1000; REQUIRED_SCHEMA_PROPERTIES = ['type']; /** * Validate a tool registry entry for MCP compliance */ validateTool(entry) { const errors = []; const warnings = []; // Basic entry validation const baseValidation = this.validateBaseEntry(entry); errors.push(...baseValidation.errors); warnings.push(...baseValidation.warnings); // Tool-specific validation if (!entry.toolDefinition) { errors.push('Tool definition is required'); return { isValid: false, errors, warnings }; } // Validate tool definition structure const toolDefValidation = this.validateToolDefinition(entry.toolDefinition); errors.push(...toolDefValidation.errors); warnings.push(...toolDefValidation.warnings); // Validate handler function if (!entry.handler || typeof entry.handler !== 'function') { errors.push('Tool handler must be a function'); } // Validate input validation function if provided if (entry.inputValidation && typeof entry.inputValidation !== 'function') { errors.push('Input validation must be a function'); } // Validate dependencies if provided if (entry.dependencies) { const depValidation = this.validateDependencies(entry.dependencies); errors.push(...depValidation.errors); warnings.push(...depValidation.warnings); } return { isValid: errors.length === 0, errors, warnings, }; } /** * Validate a prompt registry entry for MCP compliance */ validatePrompt(entry) { const errors = []; const warnings = []; // Basic entry validation const baseValidation = this.validateBaseEntry(entry); errors.push(...baseValidation.errors); warnings.push(...baseValidation.warnings); // Prompt-specific validation if (!entry.promptDefinition) { errors.push('Prompt definition is required'); return { isValid: false, errors, warnings }; } // Validate prompt definition structure const promptDefValidation = this.validatePromptDefinition(entry.promptDefinition); errors.push(...promptDefValidation.errors); warnings.push(...promptDefValidation.warnings); // Validate handler function if (!entry.handler || typeof entry.handler !== 'function') { errors.push('Prompt handler must be a function'); } // Validate input validation function if provided if (entry.inputValidation && typeof entry.inputValidation !== 'function') { errors.push('Input validation must be a function'); } // Validate dependencies if provided if (entry.dependencies) { const depValidation = this.validateDependencies(entry.dependencies); errors.push(...depValidation.errors); warnings.push(...depValidation.warnings); } return { isValid: errors.length === 0, errors, warnings, }; } /** * Validate MCP tool definition according to specification */ validateToolDefinition(tool) { const errors = []; const warnings = []; // Validate required properties if (!tool.name || typeof tool.name !== 'string') { errors.push('Tool name is required and must be a string'); } else { // Validate name format if (!this.TOOL_NAME_PATTERN.test(tool.name)) { errors.push('Tool name must start with a letter and contain only letters, numbers, underscores, and hyphens'); } if (tool.name.length > this.MAX_NAME_LENGTH) { errors.push(`Tool name must not exceed ${this.MAX_NAME_LENGTH} characters`); } } if (!tool.description || typeof tool.description !== 'string') { errors.push('Tool description is required and must be a string'); } else if (tool.description.length > this.MAX_DESCRIPTION_LENGTH) { errors.push(`Tool description must not exceed ${this.MAX_DESCRIPTION_LENGTH} characters`); } // Validate input schema if (!tool.inputSchema) { errors.push('Tool input schema is required'); } else { const schemaValidation = this.validateInputSchema(tool.inputSchema); errors.push(...schemaValidation.errors); warnings.push(...schemaValidation.warnings); } return { isValid: errors.length === 0, errors, warnings, }; } /** * Validate MCP prompt definition according to specification */ validatePromptDefinition(prompt) { const errors = []; const warnings = []; // Validate required properties if (!prompt.name || typeof prompt.name !== 'string') { errors.push('Prompt name is required and must be a string'); } else { // Validate name format if (!this.PROMPT_NAME_PATTERN.test(prompt.name)) { errors.push('Prompt name must start with a letter and contain only letters, numbers, underscores, and hyphens'); } if (prompt.name.length > this.MAX_NAME_LENGTH) { errors.push(`Prompt name must not exceed ${this.MAX_NAME_LENGTH} characters`); } } if (!prompt.description || typeof prompt.description !== 'string') { errors.push('Prompt description is required and must be a string'); } else if (prompt.description.length > this.MAX_DESCRIPTION_LENGTH) { errors.push(`Prompt description must not exceed ${this.MAX_DESCRIPTION_LENGTH} characters`); } // Validate parameters if (!prompt.parameters || !Array.isArray(prompt.parameters)) { errors.push('Prompt parameters must be an array'); } else { prompt.parameters.forEach((param, index) => { const paramValidation = this.validatePromptParameter(param, index); errors.push(...paramValidation.errors); warnings.push(...paramValidation.warnings); }); } return { isValid: errors.length === 0, errors, warnings, }; } /** * Validate naming conventions for tools and prompts */ validateNamingConvention(name, prefix) { const errors = []; const warnings = []; // Check prefix requirement if (prefix && !name.startsWith(prefix)) { errors.push(`Name '${name}' must start with prefix '${prefix}'`); } // Check for reserved names const reservedNames = ['init', 'health', 'status', 'version', 'help']; if (reservedNames.includes(name.toLowerCase())) { errors.push(`Name '${name}' is reserved and cannot be used`); } // Check for naming consistency if (name.includes('__')) { warnings.push(`Name '${name}' contains double underscores which may cause confusion`); } // Check length if (name.length < 3) { warnings.push(`Name '${name}' is very short and may not be descriptive enough`); } return { isValid: errors.length === 0, errors, warnings, }; } /** * Validate version information */ validateVersion(version) { const errors = []; const warnings = []; // Check version components if (typeof version.major !== 'number' || version.major < 0) { errors.push('Major version must be a non-negative number'); } if (typeof version.minor !== 'number' || version.minor < 0) { errors.push('Minor version must be a non-negative number'); } if (typeof version.patch !== 'number' || version.patch < 0) { errors.push('Patch version must be a non-negative number'); } // Check prerelease format if provided if (version.prerelease) { if (typeof version.prerelease !== 'string') { errors.push('Prerelease must be a string'); } else if (!/^[a-zA-Z0-9.-]+$/.test(version.prerelease)) { errors.push('Prerelease must contain only letters, numbers, dots, and hyphens'); } } // Version warnings if (version.major === 0) { warnings.push('Major version 0 indicates unstable API'); } return { isValid: errors.length === 0, errors, warnings, }; } // Private helper methods validateBaseEntry(entry) { const errors = []; const warnings = []; // Validate required fields if (!entry.id || typeof entry.id !== 'string') { errors.push('Entry ID is required and must be a string'); } if (!entry.name || typeof entry.name !== 'string') { errors.push('Entry name is required and must be a string'); } if (!entry.description || typeof entry.description !== 'string') { errors.push('Entry description is required and must be a string'); } // Validate version if (!entry.version) { errors.push('Entry version is required'); } else { const versionValidation = this.validateVersion(entry.version); errors.push(...versionValidation.errors); warnings.push(...versionValidation.warnings); } // Validate optional fields if (entry.author && typeof entry.author !== 'string') { errors.push('Author must be a string if provided'); } if (entry.tags && !Array.isArray(entry.tags)) { errors.push('Tags must be an array if provided'); } else if (entry.tags) { entry.tags.forEach((tag, index) => { if (typeof tag !== 'string') { errors.push(`Tag at index ${index} must be a string`); } }); } if (entry.deprecated && typeof entry.deprecated !== 'boolean') { errors.push('Deprecated must be a boolean if provided'); } if (entry.deprecationReason && typeof entry.deprecationReason !== 'string') { errors.push('Deprecation reason must be a string if provided'); } // Validate dates if (!(entry.createdAt instanceof Date)) { errors.push('Created date must be a Date object'); } if (!(entry.updatedAt instanceof Date)) { errors.push('Updated date must be a Date object'); } else if (entry.createdAt instanceof Date && entry.updatedAt < entry.createdAt) { errors.push('Updated date cannot be before created date'); } return { isValid: errors.length === 0, errors, warnings, }; } validateInputSchema(schema) { const errors = []; const warnings = []; // Check schema structure if (typeof schema !== 'object' || schema === null) { errors.push('Input schema must be an object'); return { isValid: false, errors, warnings }; } // Validate required schema properties this.REQUIRED_SCHEMA_PROPERTIES.forEach((prop) => { if (!(prop in schema)) { errors.push(`Input schema must have '${prop}' property`); } }); // Validate schema type if (schema.type !== 'object') { warnings.push('Input schema type should typically be "object" for MCP tools'); } // Validate properties if present if (schema.properties) { if (typeof schema.properties !== 'object') { errors.push('Schema properties must be an object'); } else { Object.keys(schema.properties).forEach((propName) => { const prop = schema.properties[propName]; if (typeof prop !== 'object' || !prop.type) { warnings.push(`Property '${propName}' should have a type specified`); } }); } } // Validate required array if present if (schema.required) { if (!Array.isArray(schema.required)) { errors.push('Schema required must be an array'); } else { schema.required.forEach((field) => { if (typeof field !== 'string') { errors.push('Required field names must be strings'); } }); } } return { isValid: errors.length === 0, errors, warnings, }; } validatePromptParameter(param, index) { const errors = []; const warnings = []; if (typeof param !== 'object' || param === null) { errors.push(`Parameter at index ${index} must be an object`); return { isValid: false, errors, warnings }; } // Validate required parameter properties if (!param.name || typeof param.name !== 'string') { errors.push(`Parameter at index ${index} must have a name (string)`); } if (!param.description || typeof param.description !== 'string') { errors.push(`Parameter at index ${index} must have a description (string)`); } if (typeof param.required !== 'boolean') { errors.push(`Parameter at index ${index} must have a required property (boolean)`); } // Additional validations for parameter name if (param.name && !this.TOOL_NAME_PATTERN.test(param.name)) { errors.push(`Parameter name '${param.name}' at index ${index} contains invalid characters`); } return { isValid: errors.length === 0, errors, warnings, }; } validateDependencies(dependencies) { const errors = []; const warnings = []; if (!Array.isArray(dependencies)) { errors.push('Dependencies must be an array'); return { isValid: false, errors, warnings }; } dependencies.forEach((dep, index) => { if (typeof dep !== 'string') { errors.push(`Dependency at index ${index} must be a string`); } else if (!this.TOOL_NAME_PATTERN.test(dep)) { errors.push(`Dependency '${dep}' at index ${index} has invalid format`); } }); // Check for circular dependencies (basic check) const uniqueDeps = new Set(dependencies); if (uniqueDeps.size !== dependencies.length) { warnings.push('Dependencies contain duplicates'); } return { isValid: errors.length === 0, errors, warnings, }; } } //# sourceMappingURL=RegistryValidator.js.map