UNPKG

christmas-mcp-image-describe

Version:

MCP server that analyzes images and provides structured metadata for website placement decisions using OpenAI GPT-4o Vision API

175 lines (153 loc) 5.6 kB
import fs from 'fs/promises'; import path from 'path'; import axios from 'axios'; import { fileTypeFromFile } from 'file-type'; import mime from 'mime'; /** * Analyzes an image using OpenAI's GPT-4o Vision API and returns structured metadata * for website placement decisions. * * @param {string} imagePath - Path to the local image file * @param {string} context - Optional context like "about section" or "homepage hero" * @param {string} apiKey - OpenAI API key * @param {string} project - OpenAI project ID (optional) * @returns {Promise<Object>} Structured image analysis result */ export async function analyzeImage(imagePath, context = '', apiKey, project = null) { if (!apiKey) { throw new Error('OpenAI API key is required'); } try { // Check if file exists await fs.access(imagePath); // Read the image file const imageBuffer = await fs.readFile(imagePath); // Detect MIME type let mimeType; try { const fileType = await fileTypeFromFile(imagePath); mimeType = fileType?.mime || mime.getType(imagePath) || 'image/jpeg'; } catch (error) { mimeType = mime.getType(imagePath) || 'image/jpeg'; } // Validate image type if (!mimeType.startsWith('image/')) { throw new Error(`Invalid image file type: ${mimeType}`); } // Convert to base64 const base64Image = imageBuffer.toString('base64'); const dataUrl = `data:${mimeType};base64,${base64Image}`; // Prepare the prompt const prompt = `Analyze this image and provide structured metadata for web development purposes. ${context ? `Context: This image is intended for use in a ${context}.` : ''} Please provide: 1. A clear, concise description of what's in the image 2. The best HTML/CSS placement suggestion (e.g., "header.logo", "section.hero", "section.about.team", "footer.contact", "article.product") 3. Relevant semantic tags that describe the content 4. Appropriate alt text for accessibility Return your response as a JSON object with exactly these fields: - description: string (brief description of image content) - placement: string (suggested HTML/CSS selector for placement) - tags: array of strings (semantic tags) - alt: string (accessibility alt text) Focus on practical web development usage and accessibility.`; // Prepare headers const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; if (project) { headers['OpenAI-Project'] = project; } // Make API request to OpenAI const response = await axios.post('https://api.openai.com/v1/chat/completions', { model: 'gpt-4o', messages: [ { role: 'user', content: [ { type: 'text', text: prompt }, { type: 'image_url', image_url: { url: dataUrl, detail: 'high' } } ] } ], max_tokens: 500, temperature: 0.3 }, { headers }); // Parse the response const content = response.data.choices[0].message.content; // Try to extract JSON from the response let result; try { // Look for JSON in the response const jsonMatch = content.match(/\{[\s\S]*\}/); if (jsonMatch) { result = JSON.parse(jsonMatch[0]); } else { throw new Error('No JSON found in response'); } } catch (parseError) { // If parsing fails, create a structured response from the text result = { description: content.split('\n')[0] || 'Image analysis available', placement: 'section.content', tags: ['image', 'content'], alt: content.split('\n')[0] || 'Analyzed image' }; } // Validate and clean the result const cleanResult = { description: result.description || 'Image content', placement: result.placement || 'section.content', tags: Array.isArray(result.tags) ? result.tags : ['image'], alt: result.alt || result.description || 'Image' }; return cleanResult; } catch (error) { if (error.response) { // OpenAI API error throw new Error(`OpenAI API error: ${error.response.status} - ${error.response.data?.error?.message || 'Unknown error'}`); } else if (error.code === 'ENOENT') { throw new Error(`Image file not found: ${imagePath}`); } else { throw new Error(`Failed to analyze image: ${error.message}`); } } } /** * Batch analyze multiple images * * @param {Array<{path: string, context?: string}>} images - Array of image objects * @param {string} apiKey - OpenAI API key * @param {string} project - OpenAI project ID (optional) * @returns {Promise<Array<Object>>} Array of analysis results */ export async function analyzeImages(images, apiKey, project = null) { const results = []; for (const image of images) { try { const result = await analyzeImage(image.path, image.context, apiKey, project); results.push({ path: image.path, success: true, data: result }); } catch (error) { results.push({ path: image.path, success: false, error: error.message }); } } return results; }