Skip to main content
Glama

sort_colors

Sort colors by hue, saturation, lightness, brightness, temperature, or frequency. Optionally group similar colors together for organized color management.

Instructions

Sort colors by various properties including hue, saturation, lightness, brightness, temperature, or frequency. Optionally group similar colors together.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
colorsYesArray of colors to sort (2-100 colors)
sort_byYesProperty to sort by
directionNoSort directionascending
group_similarNoGroup similar colors together

Implementation Reference

  • Core handler function that implements the entire 'sort_colors' tool: input validation, color parsing using UnifiedColor, metric calculations (hue, saturation, lightness, brightness, temperature, frequency), sorting by specified criteria and direction, optional grouping of similar colors using HSL distance threshold, generates detailed output with analysis, accessibility notes, recommendations, and export formats (CSS, SCSS, JSON).
    async function sortColorsHandler(
      params: unknown
    ): Promise<ToolResponse | ErrorResponse> {
      const startTime = Date.now();
    
      try {
        // Validate parameters
        const { error, value } = sortColorsSchema.validate(params);
        if (error) {
          return createErrorResponse(
            'sort_colors',
            'INVALID_PARAMETERS',
            `Invalid parameters: ${error.details.map(d => d.message).join(', ')}`,
            Date.now() - startTime,
            {
              details: error.details,
            }
          );
        }
    
        const {
          colors: colorStrings,
          sort_by,
          direction,
          group_similar,
        } = value as SortColorsParams;
    
        // Parse colors and calculate metrics
        const colorsWithMetrics: ColorWithMetrics[] = [];
        const parsedColors: UnifiedColor[] = [];
    
        for (let i = 0; i < colorStrings.length; i++) {
          const colorString = colorStrings[i];
          if (!colorString) continue;
    
          try {
            const color = new UnifiedColor(colorString);
            parsedColors.push(color);
    
            const hsl = color.hsl;
            const rgb = color.rgb;
    
            // Calculate brightness using perceived brightness formula
            const brightness = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
    
            colorsWithMetrics.push({
              original: colorString,
              color,
              index: i,
              hue: hsl.h,
              saturation: hsl.s,
              lightness: hsl.l,
              brightness,
              temperature: calculateTemperatureValue(hsl.h),
              frequency: 0, // Will be calculated below
            });
          } catch (error) {
            return createErrorResponse(
              'sort_colors',
              'INVALID_COLOR',
              `Invalid color at index ${i}: ${colorString}`,
              Date.now() - startTime,
              {
                details: {
                  colorIndex: i,
                  providedColor: colorString,
                  error: error instanceof Error ? error.message : 'Unknown error',
                },
                suggestions: [
                  'Ensure all colors are in valid format (hex, rgb, hsl, etc.)',
                  'Check color syntax and values',
                ],
              }
            );
          }
        }
    
        // Calculate frequency if needed
        if (sort_by === 'frequency') {
          const frequencyMap = calculateColorFrequency(parsedColors);
          colorsWithMetrics.forEach(item => {
            item.frequency = frequencyMap.get(item.color.hex.toLowerCase()) || 0;
          });
        }
    
        // Sort colors
        const sortedColors = [...colorsWithMetrics].sort((a, b) => {
          let valueA: number, valueB: number;
    
          switch (sort_by) {
            case 'hue':
              valueA = a.hue;
              valueB = b.hue;
              break;
            case 'saturation':
              valueA = a.saturation;
              valueB = b.saturation;
              break;
            case 'lightness':
              valueA = a.lightness;
              valueB = b.lightness;
              break;
            case 'brightness':
              valueA = a.brightness;
              valueB = b.brightness;
              break;
            case 'temperature':
              valueA = a.temperature;
              valueB = b.temperature;
              break;
            case 'frequency':
              valueA = a.frequency;
              valueB = b.frequency;
              break;
            default:
              valueA = a.hue;
              valueB = b.hue;
          }
    
          const comparison = valueA - valueB;
          return direction === 'ascending' ? comparison : -comparison;
        });
    
        // Group similar colors if requested
        const result: SortResult = {
          sorted_colors: sortedColors.map(item => ({
            original: item.original,
            hex: item.color.hex,
            rgb: `rgb(${item.color.rgb.r}, ${item.color.rgb.g}, ${item.color.rgb.b})`,
            hsl: `hsl(${item.color.hsl.h}, ${item.color.hsl.s}%, ${item.color.hsl.l}%)`,
            hsv: `hsv(${item.color.hsv.h}, ${item.color.hsv.s}%, ${item.color.hsv.v}%)`,
            metrics: {
              hue: Math.round(item.hue * 10) / 10,
              saturation: Math.round(item.saturation * 10) / 10,
              lightness: Math.round(item.lightness * 10) / 10,
              brightness: Math.round(item.brightness * 10) / 10,
              temperature: Math.round(item.temperature * 10) / 10,
              frequency: item.frequency,
            },
            original_index: item.index,
          })),
          sort_criteria: sort_by,
          direction,
          total_colors: colorStrings.length,
        };
    
        if (group_similar) {
          const groups = groupSimilarColors(sortedColors);
          result.groups = groups.map((group, groupIndex) => ({
            group_id: groupIndex + 1,
            colors: group.map(item => ({
              original: item.original,
              hex: item.color.hex,
              rgb: `rgb(${item.color.rgb.r}, ${item.color.rgb.g}, ${item.color.rgb.b})`,
              hsl: `hsl(${item.color.hsl.h}, ${item.color.hsl.s}%, ${item.color.hsl.l}%)`,
              hsv: `hsv(${item.color.hsv.h}, ${item.color.hsv.s}%, ${item.color.hsv.v}%)`,
              metrics: {
                hue: Math.round(item.hue * 10) / 10,
                saturation: Math.round(item.saturation * 10) / 10,
                lightness: Math.round(item.lightness * 10) / 10,
                brightness: Math.round(item.brightness * 10) / 10,
                temperature: Math.round(item.temperature * 10) / 10,
                frequency: item.frequency,
              },
              original_index: item.index,
            })),
            group_size: group.length,
            average_metrics: {
              hue:
                Math.round(
                  (group.reduce((sum, item) => sum + item.hue, 0) / group.length) *
                    10
                ) / 10,
              saturation:
                Math.round(
                  (group.reduce((sum, item) => sum + item.saturation, 0) /
                    group.length) *
                    10
                ) / 10,
              lightness:
                Math.round(
                  (group.reduce((sum, item) => sum + item.lightness, 0) /
                    group.length) *
                    10
                ) / 10,
              brightness:
                Math.round(
                  (group.reduce((sum, item) => sum + item.brightness, 0) /
                    group.length) *
                    10
                ) / 10,
              temperature:
                Math.round(
                  (group.reduce((sum, item) => sum + item.temperature, 0) /
                    group.length) *
                    10
                ) / 10,
            },
          }));
          result.total_groups = groups.length;
        }
    
        const executionTime = Date.now() - startTime;
    
        // Generate analysis
        const analysis = {
          color_distribution: {
            hue_range: {
              min: Math.min(...colorsWithMetrics.map(c => c.hue)),
              max: Math.max(...colorsWithMetrics.map(c => c.hue)),
            },
            saturation_range: {
              min: Math.min(...colorsWithMetrics.map(c => c.saturation)),
              max: Math.max(...colorsWithMetrics.map(c => c.saturation)),
            },
            lightness_range: {
              min: Math.min(...colorsWithMetrics.map(c => c.lightness)),
              max: Math.max(...colorsWithMetrics.map(c => c.lightness)),
            },
            brightness_range: {
              min: Math.min(...colorsWithMetrics.map(c => c.brightness)),
              max: Math.max(...colorsWithMetrics.map(c => c.brightness)),
            },
          },
          dominant_characteristics: {
            most_common_hue_range: getMostCommonHueRange(colorsWithMetrics),
            average_saturation:
              Math.round(
                (colorsWithMetrics.reduce((sum, c) => sum + c.saturation, 0) /
                  colorsWithMetrics.length) *
                  10
              ) / 10,
            average_lightness:
              Math.round(
                (colorsWithMetrics.reduce((sum, c) => sum + c.lightness, 0) /
                  colorsWithMetrics.length) *
                  10
              ) / 10,
          },
        };
    
        // Generate accessibility notes
        const accessibilityNotes: string[] = [];
        const darkColors = colorsWithMetrics.filter(c => c.lightness < 30).length;
        const lightColors = colorsWithMetrics.filter(c => c.lightness > 70).length;
        const midColors = colorsWithMetrics.length - darkColors - lightColors;
    
        accessibilityNotes.push(
          `Color distribution: ${darkColors} dark, ${midColors} medium, ${lightColors} light colors`
        );
    
        if (darkColors > lightColors * 2) {
          accessibilityNotes.push(
            'Palette is dark-heavy - ensure sufficient light colors for contrast'
          );
        } else if (lightColors > darkColors * 2) {
          accessibilityNotes.push(
            'Palette is light-heavy - ensure sufficient dark colors for contrast'
          );
        }
    
        // Generate recommendations
        const recommendations: string[] = [];
        if (sort_by === 'hue') {
          recommendations.push(
            'Hue sorting creates rainbow-like color progressions'
          );
        } else if (sort_by === 'lightness') {
          recommendations.push(
            'Lightness sorting helps identify contrast relationships'
          );
        } else if (sort_by === 'saturation') {
          recommendations.push('Saturation sorting groups vivid and muted colors');
        }
    
        if (group_similar) {
          recommendations.push(
            'Similar color grouping helps identify redundant colors in palettes'
          );
        }
    
        // Add export formats
        const exportFormats = {
          css: result.sorted_colors
            .map((c, i: number) => `--color-${i + 1}: ${c.hex};`)
            .join('\n'),
    
          scss: result.sorted_colors
            .map((c, i: number) => `$color-${i + 1}: ${c.hex};`)
            .join('\n'),
    
          json: {
            sort_criteria: sort_by,
            direction,
            colors: result.sorted_colors.map(c => ({
              hex: c.hex,
              metrics: c.metrics,
            })),
          },
        };
    
        return createSuccessResponse(
          'sort_colors',
          {
            ...result,
            analysis,
          },
          executionTime,
          {
            colorSpaceUsed: 'hsl',
            accessibilityNotes: accessibilityNotes,
            recommendations,
            exportFormats,
          }
        );
      } catch (error) {
        const executionTime = Date.now() - startTime;
        logger.error('Error in sort_colors tool', { error: error as Error });
    
        return createErrorResponse(
          'sort_colors',
          'PROCESSING_ERROR',
          'An error occurred while sorting colors',
          executionTime,
          {
            details: {
              error: error instanceof Error ? error.message : 'Unknown error',
            },
            suggestions: [
              'Check that all input colors are valid',
              'Verify sort criteria and direction parameters',
              'Try with fewer colors if processing large sets',
            ],
          }
        );
      }
    }
  • Joi validation schema defining input parameters for the sort_colors tool: colors array (2-100 strings), sort_by enum, direction, group_similar boolean.
    const sortColorsSchema = Joi.object({
      colors: Joi.array()
        .items(Joi.string().required())
        .min(2)
        .max(100)
        .required()
        .messages({
          'array.min': 'At least 2 colors are required for sorting',
          'array.max': 'Maximum 100 colors can be sorted at once',
        }),
      sort_by: Joi.string()
        .valid(
          'hue',
          'saturation',
          'lightness',
          'brightness',
          'temperature',
          'frequency'
        )
        .required()
        .messages({
          'any.only':
            'Sort criteria must be one of: hue, saturation, lightness, brightness, temperature, frequency',
        }),
      direction: Joi.string().valid('ascending', 'descending').default('ascending'),
      group_similar: Joi.boolean().default(false),
    });
  • JSON Schema (MCP tool parameters) defining the input structure for sort_colors tool.
    parameters: {
      type: 'object',
      properties: {
        colors: {
          type: 'array',
          items: { type: 'string' },
          minItems: 2,
          maxItems: 100,
          description: 'Array of colors to sort (2-100 colors)',
        },
        sort_by: {
          type: 'string',
          enum: [
            'hue',
            'saturation',
            'lightness',
            'brightness',
            'temperature',
            'frequency',
          ],
          description: 'Property to sort by',
        },
        direction: {
          type: 'string',
          enum: ['ascending', 'descending'],
          default: 'ascending',
          description: 'Sort direction',
        },
        group_similar: {
          type: 'boolean',
          default: false,
          description: 'Group similar colors together',
        },
      },
      required: ['colors', 'sort_by'],
    },
  • Registration of the sortColorsTool in the central tool registry.
    toolRegistry.registerTool(sortColorsTool);
  • Import of the sortColorsTool for registration.
    import { sortColorsTool } from './sort-colors';

Latest Blog Posts

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/keyurgolani/ColorMcp'

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