Skip to main content
Glama
sam2332

Placeholder Image Generator

by sam2332
index.ts6.67 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { createCanvas } from "canvas"; import fs from "fs"; import path from "path"; // Create server instance const server = new McpServer({ name: "generate-placeholder", version: "1.0.0", capabilities: { tools: {}, }, }); // Helper function to validate color function isValidColor(color: string): boolean { // Simple validation for hex colors, rgb colors, and common named colors const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; const rgbPattern = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/; const rgbaPattern = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([01]|0?\.\d+)\s*\)$/; const namedColors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple', 'pink', 'brown', 'black', 'white', 'gray', 'grey']; return hexPattern.test(color) || rgbPattern.test(color) || rgbaPattern.test(color) || namedColors.includes(color.toLowerCase()); } // Helper function to get contrasting text color function getContrastingTextColor(backgroundColor: string): string { // Simple logic: if background is dark, use white text; if light, use black text const darkColors = ['black', 'navy', 'darkblue', 'darkgreen', 'purple', 'darkred', 'brown']; if (backgroundColor.startsWith('#')) { // For hex colors, calculate brightness const hex = backgroundColor.replace('#', ''); const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); const brightness = (r * 299 + g * 587 + b * 114) / 1000; return brightness > 128 ? 'black' : 'white'; } else if (backgroundColor.startsWith('rgb')) { // For RGB colors, extract values and calculate brightness const match = backgroundColor.match(/\d+/g); if (match && match.length >= 3) { const r = parseInt(match[0]); const g = parseInt(match[1]); const b = parseInt(match[2]); const brightness = (r * 299 + g * 587 + b * 114) / 1000; return brightness > 128 ? 'black' : 'white'; } } // For named colors return darkColors.includes(backgroundColor.toLowerCase()) ? 'white' : 'black'; } // Register the generate placeholder image tool server.tool( "generate-placeholder-image", "Generate a placeholder image with specified dimensions, color, and text", { filename: z.string().describe("The filename for the generated image (including extension)"), width: z.number().int().min(1).max(4096).describe("Width of the image in pixels"), height: z.number().int().min(1).max(4096).describe("Height of the image in pixels"), color: z.string().describe("Background color (hex, rgb, or named color)"), text: z.string().describe("Text to display on the placeholder image"), }, async (params: { filename: string; width: number; height: number; color: string; text: string }) => { const { filename, width, height, color, text } = params; try { // Validate inputs if (!isValidColor(color)) { return { content: [ { type: "text", text: `Error: Invalid color format '${color}'. Please use hex (#RRGGBB), rgb(r,g,b), or named colors.`, }, ], }; } // Ensure filename has a supported extension const supportedExtensions = ['.png', '.jpg', '.jpeg']; const ext = path.extname(filename).toLowerCase(); if (!supportedExtensions.includes(ext)) { return { content: [ { type: "text", text: `Error: Unsupported file extension '${ext}'. Supported extensions: ${supportedExtensions.join(', ')}`, }, ], }; } // Create canvas const canvas = createCanvas(width, height); const ctx = canvas.getContext('2d'); // Set background color ctx.fillStyle = color; ctx.fillRect(0, 0, width, height); // Calculate font size based on image dimensions const fontSize = Math.min(width, height) / 10; ctx.font = `${fontSize}px Arial, sans-serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // Set text color (contrasting with background) ctx.fillStyle = getContrastingTextColor(color); // Draw text in the center const centerX = width / 2; const centerY = height / 2; // Handle multi-line text if it's too long const maxWidth = width * 0.8; const words = text.split(' '); const lines: string[] = []; let currentLine = ''; for (const word of words) { const testLine = currentLine + (currentLine ? ' ' : '') + word; const metrics = ctx.measureText(testLine); if (metrics.width > maxWidth && currentLine) { lines.push(currentLine); currentLine = word; } else { currentLine = testLine; } } lines.push(currentLine); // Draw each line const lineHeight = fontSize * 1.2; const startY = centerY - ((lines.length - 1) * lineHeight) / 2; lines.forEach((line, index) => { ctx.fillText(line, centerX, startY + index * lineHeight); }); // Add dimensions text at the bottom const dimensionsText = `${width} × ${height}`; const smallFontSize = Math.min(fontSize * 0.4, 24); ctx.font = `${smallFontSize}px Arial, sans-serif`; ctx.fillText(dimensionsText, centerX, height - smallFontSize); // Save the image const buffer = ext === '.png' ? canvas.toBuffer('image/png') : canvas.toBuffer('image/jpeg'); fs.writeFileSync(filename, buffer); return { content: [ { type: "text", text: `Successfully generated placeholder image: ${filename} (${width}×${height}) with background color '${color}' and text '${text}'`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating placeholder image: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } } ); // Main function to run the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Generate Placeholder Image MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });

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/sam2332/mcp-server-generate-placeholder'

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