Skip to main content
Glama

Enhanced Coolify MCP Server

parameter-validator.tsโ€ข12.1 kB
import debug from 'debug'; import { ErrorHandler, EnhancedError, ValidationResult } from './error-handler.js'; const log = debug('coolify:parameter-validator'); export interface ParameterDefinition { name: string; type: 'string' | 'number' | 'boolean' | 'object' | 'array'; required: boolean; defaultProvider?: () => Promise<any> | any; validator?: (value: any) => boolean | string; description: string; examples?: any[]; enumValues?: string[]; } export interface ToolDefinition { name: string; description: string; parameters: ParameterDefinition[]; examples?: Record<string, any>[]; } export class ParameterValidator { private static toolDefinitions: Map<string, ToolDefinition> = new Map(); /** * Register tool parameter definitions */ static registerTool(definition: ToolDefinition): void { log(`Registering tool: ${definition.name}`); this.toolDefinitions.set(definition.name, definition); } /** * Validate parameters for a specific tool */ static validateParameters( toolName: string, parameters: Record<string, any> ): ValidationResult { const definition = this.toolDefinitions.get(toolName); if (!definition) { return { valid: false, errors: [`Unknown tool: ${toolName}`], warnings: [] }; } const errors: string[] = []; const warnings: string[] = []; // Check required parameters for (const paramDef of definition.parameters) { if (paramDef.required && !(paramDef.name in parameters)) { errors.push(`Missing required parameter: ${paramDef.name}`); continue; } const value = parameters[paramDef.name]; if (value !== undefined) { // Type validation const typeError = this.validateType(paramDef.name, value, paramDef.type); if (typeError) { errors.push(typeError); continue; } // Enum validation if (paramDef.enumValues && !paramDef.enumValues.includes(value)) { errors.push( `Invalid value for ${paramDef.name}. Expected one of: ${paramDef.enumValues.join(', ')}` ); continue; } // Custom validation if (paramDef.validator) { const validationResult = paramDef.validator(value); if (validationResult !== true) { const message = typeof validationResult === 'string' ? validationResult : `Invalid value for ${paramDef.name}`; errors.push(message); } } } } // Check for unknown parameters for (const paramName in parameters) { if (!definition.parameters.some(p => p.name === paramName)) { warnings.push(`Unknown parameter: ${paramName}`); } } return { valid: errors.length === 0, errors, warnings }; } /** * Enrich parameters with default values */ static async enrichWithDefaults( toolName: string, parameters: Record<string, any> ): Promise<Record<string, any>> { const definition = this.toolDefinitions.get(toolName); if (!definition) { return parameters; } const enriched = { ...parameters }; for (const paramDef of definition.parameters) { if (!(paramDef.name in enriched) && paramDef.defaultProvider) { try { const defaultValue = typeof paramDef.defaultProvider === 'function' ? await paramDef.defaultProvider() : paramDef.defaultProvider; if (defaultValue !== undefined) { enriched[paramDef.name] = defaultValue; log(`Applied default value for ${paramDef.name}: ${defaultValue}`); } } catch (error) { log(`Failed to get default value for ${paramDef.name}: ${error}`); } } } return enriched; } /** * Get parameter documentation for a tool */ static getToolDocumentation(toolName: string): string { const definition = this.toolDefinitions.get(toolName); if (!definition) { return `Unknown tool: ${toolName}`; } let doc = `# ${definition.name}\n\n${definition.description}\n\n## Parameters\n\n`; for (const param of definition.parameters) { doc += `### ${param.name} (${param.type})${param.required ? ' *required*' : ''}\n`; doc += `${param.description}\n\n`; if (param.enumValues) { doc += `**Valid values:** ${param.enumValues.join(', ')}\n\n`; } if (param.examples && param.examples.length > 0) { doc += `**Examples:** ${param.examples.map(e => JSON.stringify(e)).join(', ')}\n\n`; } } if (definition.examples && definition.examples.length > 0) { doc += `## Usage Examples\n\n`; definition.examples.forEach((example, index) => { doc += `### Example ${index + 1}\n\n`; doc += '```json\n'; doc += JSON.stringify(example, null, 2); doc += '\n```\n\n'; }); } return doc; } /** * Get parameter suggestions for a tool */ static getParameterSuggestions( toolName: string, currentParameters: Record<string, any> = {} ): string[] { const definition = this.toolDefinitions.get(toolName); if (!definition) { return []; } const suggestions: string[] = []; // Suggest missing required parameters for (const param of definition.parameters) { if (param.required && !(param.name in currentParameters)) { suggestions.push(`Add required parameter: ${param.name} (${param.description})`); } } // Suggest optional parameters that might be useful for (const param of definition.parameters) { if (!param.required && !(param.name in currentParameters)) { if (param.examples && param.examples.length > 0) { suggestions.push( `Consider adding ${param.name}: ${JSON.stringify(param.examples[0])}` ); } } } return suggestions; } /** * Validate parameter type */ private static validateType( paramName: string, value: any, expectedType: string ): string | null { const actualType = Array.isArray(value) ? 'array' : typeof value; if (expectedType === 'object' && actualType === 'object' && value !== null) { return null; // Valid object } if (actualType !== expectedType) { return `Parameter ${paramName} expected ${expectedType}, got ${actualType}`; } return null; } /** * Create enhanced validation error with suggestions */ static createValidationError( toolName: string, validationResult: ValidationResult, parameters: Record<string, any> ): EnhancedError { const suggestions = [ ...this.getParameterSuggestions(toolName, parameters), `Use getToolDocumentation('${toolName}') for detailed parameter information` ]; return ErrorHandler.createValidationError( `Parameter validation failed for ${toolName}: ${validationResult.errors.join(', ')}`, { tool: toolName, errors: validationResult.errors, warnings: validationResult.warnings, parameters, suggestions } ); } /** * Initialize with default tool definitions */ static initializeDefaultTools(): void { // Server management tools this.registerTool({ name: 'list_servers', description: 'List all Coolify servers', parameters: [], examples: [{}] }); this.registerTool({ name: 'get_server', description: 'Get details about a specific Coolify server', parameters: [ { name: 'uuid', type: 'string', required: true, description: 'UUID of the server to get details for', validator: (value) => typeof value === 'string' && value.length > 0 } ], examples: [ { uuid: 'a8gwwc0o0w4s00wgoogsko88' } ] }); // Project management tools this.registerTool({ name: 'create_project', description: 'Create a new Coolify project', parameters: [ { name: 'name', type: 'string', required: true, description: 'Name of the project', validator: (value) => typeof value === 'string' && value.length > 0, examples: ['my-project', 'web-app'] }, { name: 'description', type: 'string', required: false, description: 'Description of the project', examples: ['My awesome project', 'Web application deployment'] } ], examples: [ { name: 'my-project', description: 'My awesome project' }, { name: 'web-app' } ] }); // Service creation tool with enhanced validation this.registerTool({ name: 'create_service', description: 'Create a new Coolify service', parameters: [ { name: 'type', type: 'string', required: true, description: 'Type of service to create', enumValues: [ 'activepieces', 'appsmith', 'appwrite', 'authentik', 'babybuddy', 'budge', 'changedetection', 'chatwoot', 'classicpress-with-mariadb', 'classicpress-with-mysql', 'classicpress-without-database', 'cloudflared', 'code-server', 'dashboard', 'directus', 'directus-with-postgresql', 'docker-registry', 'docuseal', 'docuseal-with-postgres', 'dokuwiki', 'duplicati', 'emby', 'embystat', 'fider', 'filebrowser', 'firefly', 'formbricks', 'ghost', 'gitea', 'gitea-with-mariadb', 'gitea-with-mysql', 'gitea-with-postgresql', 'glance', 'glances', 'glitchtip', 'grafana', 'grafana-with-postgresql', 'grocy', 'heimdall', 'homepage', 'jellyfin', 'kuzzle', 'listmonk', 'logto', 'mediawiki', 'meilisearch', 'metabase', 'metube', 'minio', 'moodle', 'n8n', 'n8n-with-postgresql', 'next-image-transformation', 'nextcloud', 'nocodb', 'odoo', 'openblocks', 'pairdrop', 'penpot', 'phpmyadmin', 'pocketbase', 'posthog', 'reactive-resume', 'rocketchat', 'shlink', 'slash', 'snapdrop', 'statusnook', 'stirling-pdf', 'supabase', 'syncthing', 'tolgee', 'trigger', 'trigger-with-external-database', 'twenty', 'umami', 'unleash-with-postgresql', 'unleash-without-database', 'uptime-kuma', 'vaultwarden', 'vikunja', 'weblate', 'whoogle', 'wordpress-with-mariadb', 'wordpress-with-mysql', 'wordpress-without-database' ] }, { name: 'project_uuid', type: 'string', required: true, description: 'UUID of the project to create the service in' }, { name: 'server_uuid', type: 'string', required: true, description: 'UUID of the server to deploy the service on' }, { name: 'name', type: 'string', required: false, description: 'Custom name for the service' }, { name: 'description', type: 'string', required: false, description: 'Description of the service' }, { name: 'environment_name', type: 'string', required: false, description: 'Name of the environment (defaults to "production")', defaultProvider: () => 'production' }, { name: 'instant_deploy', type: 'boolean', required: false, description: 'Whether to deploy the service immediately', defaultProvider: () => false } ], examples: [ { type: 'minio', project_uuid: 'project-uuid-here', server_uuid: 'server-uuid-here', name: 'my-storage', description: 'Object storage service' } ] }); log('Initialized default tool definitions'); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dazeb/coolify-mcp-enhanced'

If you have feedback or need assistance with the MCP directory API, please join our Discord server