Skip to main content
Glama
ewilderj

Fountain Pen Ink MCP Server

get_color_palette

Generate themed or harmony-based fountain pen ink palettes using predefined themes, custom color lists, or color harmony rules from a base color.

Instructions

Generate a themed or harmony-based palette of inks. Supports three modes: 1) Predefined themes (warm, cool, earth, ocean, autumn, spring, summer, winter, pastel, vibrant, monochrome, sunset, forest), 2) Custom hex color lists (comma-separated), 3) Color harmony generation from a base hex color.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
themeYesTheme name (e.g., "warm", "ocean"), comma-separated hex colors (e.g., "#FF0000,#00FF00"), or single hex color for harmony generation (e.g., "#FF0000").
palette_sizeNoNumber of inks in the palette (default: 5)
harmonyNoColor harmony rule to apply when theme is a single hex color. Options: "complementary", "analogous", "triadic", "split-complementary". Requires theme to be a valid hex color.

Implementation Reference

  • Main handler function that executes the get_color_palette tool. Handles theme parsing, color harmony generation, and finds closest matching fountain pen inks for each palette color.
    private getColorPalette(
      theme: string,
      paletteSize: number,
      harmony?: Harmony,
    ): MCPTextResponse {
      const themeColors: { [key: string]: [number, number, number][] } = {
        warm: [
          [255, 100, 50],
          [255, 150, 0],
          [200, 80, 80],
          [180, 120, 60],
          [220, 180, 100],
        ],
        cool: [
          [50, 150, 255],
          [100, 200, 200],
          [150, 100, 255],
          [80, 180, 150],
          [120, 120, 200],
        ],
        earth: [
          [139, 69, 19],
          [160, 82, 45],
          [210, 180, 140],
          [107, 142, 35],
          [85, 107, 47],
        ],
        ocean: [
          [0, 119, 190],
          [0, 150, 136],
          [72, 201, 176],
          [135, 206, 235],
          [25, 25, 112],
        ],
        autumn: [
          [255, 140, 0],
          [255, 69, 0],
          [220, 20, 60],
          [184, 134, 11],
          [139, 69, 19],
        ],
        spring: [
          [154, 205, 50],
          [124, 252, 0],
          [173, 255, 47],
          [50, 205, 50],
          [0, 255, 127],
        ],
        summer: [
          [255, 235, 59],
          [255, 193, 7],
          [76, 175, 80],
          [139, 195, 74],
          [3, 169, 244],
        ],
        winter: [
          [224, 224, 224],
          [144, 164, 174],
          [96, 125, 139],
          [33, 150, 243],
          [0, 0, 128],
        ],
        pastel: [
          [255, 204, 204],
          [204, 255, 204],
          [204, 204, 255],
          [255, 255, 204],
          [255, 204, 255],
        ],
        vibrant: [
          [255, 0, 0],
          [0, 255, 0],
          [0, 0, 255],
          [255, 255, 0],
          [255, 0, 255],
        ],
        monochrome: [
          [255, 255, 255],
          [224, 224, 224],
          [192, 192, 192],
          [128, 128, 128],
          [64, 64, 64],
          [0, 0, 0],
        ],
        sunset: [
          [255, 224, 130],
          [255, 170, 85],
          [255, 110, 80],
          [200, 80, 120],
          [100, 60, 110],
        ],
        forest: [
          [34, 85, 34],
          [20, 60, 20],
          [60, 100, 60],
          [100, 140, 100],
          [140, 180, 140],
        ],
      };
    
      let targetColors: [number, number, number][];
      const lowerCaseTheme = theme.toLowerCase();
    
      if (harmony) {
        try {
          const baseRgb = hexToRgb(theme);
          const baseHsl = rgbToHsl(baseRgb);
          const harmonyHsl = generateHarmonyColors(baseHsl, harmony);
          targetColors = harmonyHsl.map((hsl) => hslToRgb(hsl));
        } catch {
          throw new Error('Invalid base color for harmony rule. Please use a single valid hex code.');
        }
      } else if (themeColors[lowerCaseTheme]) {
        targetColors = themeColors[lowerCaseTheme];
      } else if (theme.startsWith('#') || theme.includes(',')) {
        try {
          targetColors = theme.split(',').map((hex) => hexToRgb(hex.trim()));
        } catch {
          throw new Error(
            'Invalid custom palette format. Please use a comma-separated list of hex codes, e.g., "#FF0000,#00FF00,#0000FF"',
          );
        }
      } else {
        throw new Error(
          `Unknown theme: "${theme}". Available themes are: ${Object.keys(themeColors).join(', ')}`,
        );
      }
    
      const paletteInks: SearchResult[] = [];
      const usedInkIds = new Set<string>();
    
      for (let i = 0; i < Math.min(paletteSize, targetColors.length); i++) {
        const targetRgb = targetColors[i];
        const closestInks = findClosestInks(targetRgb, this.inkColors, 5).filter(
          (ink) => !usedInkIds.has(ink.ink_id),
        );
    
        if (closestInks.length > 0) {
          const ink = closestInks[0];
          usedInkIds.add(ink.ink_id);
          const metadata = this.getInkMetadata(ink.ink_id);
          paletteInks.push(createSearchResult(ink, metadata, ink.distance));
        }
      }
    
      const palette: PaletteResult = {
        theme,
        inks: paletteInks,
        description: `A curated palette of ${paletteInks.length} fountain pen inks matching the ${theme} theme.`,
      };
    
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify(palette, null, 2),
          },
        ],
      } satisfies MCPTextResponse;
    }
  • Tool schema definition provided in listTools response, specifying input parameters: theme (required), palette_size, harmony.
    {
      name: 'get_color_palette',
      description:
        'Generate a themed or harmony-based palette of inks. Supports three modes: 1) Predefined themes (warm, cool, earth, ocean, autumn, spring, summer, winter, pastel, vibrant, monochrome, sunset, forest), 2) Custom hex color lists (comma-separated), 3) Color harmony generation from a base hex color.',
      inputSchema: {
        type: 'object',
        properties: {
          theme: {
            type: 'string',
            description:
              'Theme name (e.g., "warm", "ocean"), comma-separated hex colors (e.g., "#FF0000,#00FF00"), or single hex color for harmony generation (e.g., "#FF0000").',
          },
          palette_size: {
            type: 'number',
            description: 'Number of inks in the palette (default: 5)',
            default: 5,
          },
          harmony: {
            type: 'string',
            description:
              'Color harmony rule to apply when theme is a single hex color. Options: "complementary", "analogous", "triadic", "split-complementary". Requires theme to be a valid hex color.',
            enum: ['complementary', 'analogous', 'triadic', 'split-complementary'],
          },
        },
        required: ['theme'],
        additionalProperties: false,
      },
    },
  • src/index.ts:295-300 (registration)
    Tool call dispatch in the CallToolRequestSchema handler, routing to the getColorPalette implementation.
    case 'get_color_palette':
      return this.getColorPalette(
        args.theme as string,
        (args.palette_size as number) || 5,
        args.harmony as Harmony,
      );
  • Helper function to generate color harmony variants (complementary, analogous, etc.) from base HSL color, used when harmony parameter is provided.
    export function generateHarmonyColors(
      baseHsl: [number, number, number],
      harmony: 'complementary' | 'analogous' | 'triadic' | 'split-complementary',
    ): [number, number, number][] {
      const [h, s, l] = baseHsl;
      const harmonyHues: { [key: string]: number[] } = {
        complementary: [h, (h + 180) % 360],
        analogous: [h, (h + 30) % 360, (h + 330) % 360],
        triadic: [h, (h + 120) % 360, (h + 240) % 360],
        'split-complementary': [h, (h + 150) % 360, (h + 210) % 360],
      };
    
      const hues = harmonyHues[harmony] || [h];
      return hues.map((hue) => [hue, s, l]);
    }
  • Core helper to find closest fountain pen inks to a target RGB color using distance metric, used repeatedly in palette generation.
    export function findClosestInks(
      targetRgb: [number, number, number],
      inkColors: InkColor[],
      maxResults: number = 20,
    ): InkWithDistance[] {
      const distances = inkColors.map((ink) => ({
        ...ink,
        distance: calculateColorDistance(targetRgb, ink.rgb), // Now both are RGB!
      }));
    
      // Sort by distance (closest first)
      distances.sort((a, b) => a.distance - b.distance);
    
      return distances.slice(0, maxResults);
    }

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/ewilderj/inks-mcp'

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