Skip to main content
Glama
kareemaly

AI Image MCP Server

by kareemaly
image_description_tools.py12.3 kB
import os from pathlib import Path from server import mcp from utils.path_utils import resolve_path, validate_image_path from utils.openai_client import get_openai_client, encode_image_to_base64, is_valid_image_format, get_image_info from utils.cache_utils import get_cache def _analyze_image_with_cache(resolved_path: Path, prompt: str, operation: str, params: dict) -> str: """ Internal function to analyze image with caching support. Args: resolved_path: Resolved path to the image file prompt: Prompt for the analysis operation: Operation type for cache key params: Additional parameters for cache key Returns: Analysis result (from cache or OpenAI API) """ cache = get_cache() # Try to get cached result first cached_result = cache.get_cached_result(resolved_path, operation, params) if cached_result: return cached_result + "\n\n[Result retrieved from cache]" # Get image info image_info = get_image_info(resolved_path) if "error" in image_info: return f"Error reading image: {image_info['error']}" # Check file size (OpenAI has limits) max_size = 20 * 1024 * 1024 # 20MB if image_info.get("file_size", 0) > max_size: return f"Error: Image file is too large ({image_info['file_size']:,} bytes). Maximum size is {max_size:,} bytes." # Get OpenAI client and analyze image client = get_openai_client() base64_image = encode_image_to_base64(resolved_path) response = client.chat.completions.create( model="gpt-4o", messages=[ { "role": "user", "content": [ { "type": "text", "text": prompt }, { "type": "image_url", "image_url": { "url": f"data:image/{image_info.get('format', 'jpeg').lower()};base64,{base64_image}" } } ] } ], max_tokens=1000 ) description = response.choices[0].message.content # Format result result = f"Image Analysis for '{resolved_path.name}':\n\nImage Info: {image_info['size'][0]}x{image_info['size'][1]} pixels, {image_info.get('format', 'Unknown')} format\n\nDescription:\n{description}" # Store result in cache cache.store_result(resolved_path, operation, params, result) return result @mcp.tool() def describe_image(working_dir: str, image_path: str, prompt: str = "Please describe this image in detail.") -> str: """ Analyze an image and provide a detailed description using OpenAI's Vision API. Uses caching to avoid repeated API calls for the same image and prompt. Args: working_dir: Absolute path to the working directory for file operations image_path: Path to the image file relative to working_dir (supports PNG, JPEG, GIF, WebP) prompt: Custom prompt for the image analysis (optional) Returns: Detailed description of the image content """ try: # Validate working directory working_path = Path(working_dir) if not working_path.is_absolute(): return f"Error: working_dir must be an absolute path, got: {working_dir}" if not working_path.exists(): return f"Error: working_dir does not exist: {working_dir}" if not working_path.is_dir(): return f"Error: working_dir is not a directory: {working_dir}" # Validate image path with clear error messages is_valid, error_message, resolved_path = validate_image_path(image_path, "read", working_dir) if not is_valid: return error_message if not is_valid_image_format(resolved_path): return ( f"Error: Unsupported image format for analysis.\n" f"• File: '{image_path}'\n" f"• Supported formats: PNG, JPEG, JPG, GIF, WebP\n" f"• Suggestion: Convert the image to a supported format or use a different image file." ) # Use cached analysis params = {"prompt": prompt} return _analyze_image_with_cache(resolved_path, prompt, "describe", params) except Exception as e: return f"Error analyzing image: {str(e)}" @mcp.tool() def analyze_image_content(working_dir: str, image_path: str, analysis_type: str = "general") -> str: """ Analyze specific aspects of an image using OpenAI's Vision API. Uses caching to avoid repeated API calls for the same image and analysis type. Args: working_dir: Absolute path to the working directory for file operations image_path: Path to the image file relative to working_dir analysis_type: Type of analysis - "general", "objects", "text", "colors", "composition", "emotions" Returns: Targeted analysis of the image based on the specified type """ prompts = { "general": "Provide a comprehensive description of this image, including objects, people, setting, and overall composition.", "objects": "Identify and list all objects, items, and things visible in this image. Be specific and detailed.", "text": "Extract and transcribe any text, signs, labels, or written content visible in this image.", "colors": "Analyze the color palette, dominant colors, and color scheme of this image. Describe the mood created by the colors.", "composition": "Analyze the composition, framing, perspective, lighting, and artistic elements of this image.", "emotions": "Describe the emotions, mood, and feelings conveyed by this image. What emotional response might it evoke?" } if analysis_type not in prompts: return f"Error: Invalid analysis type. Choose from: {', '.join(prompts.keys())}" try: # Validate working directory working_path = Path(working_dir) if not working_path.is_absolute(): return f"Error: working_dir must be an absolute path, got: {working_dir}" if not working_path.exists(): return f"Error: working_dir does not exist: {working_dir}" if not working_path.is_dir(): return f"Error: working_dir is not a directory: {working_dir}" # Validate image path with clear error messages is_valid, error_message, resolved_path = validate_image_path(image_path, "read", working_dir) if not is_valid: return error_message if not is_valid_image_format(resolved_path): return ( f"Error: Unsupported image format for analysis.\n" f"• File: '{image_path}'\n" f"• Supported formats: PNG, JPEG, JPG, GIF, WebP\n" f"• Suggestion: Convert the image to a supported format or use a different image file." ) # Use cached analysis prompt = prompts[analysis_type] params = {"analysis_type": analysis_type, "prompt": prompt} return _analyze_image_with_cache(resolved_path, prompt, "analyze", params) except Exception as e: return f"Error analyzing image: {str(e)}" @mcp.tool() def compare_images(working_dir: str, image1_path: str, image2_path: str, comparison_focus: str = "similarities and differences") -> str: """ Compare two images and highlight their similarities and differences. Uses caching for individual image analysis to improve performance. Args: working_dir: Absolute path to the working directory for file operations image1_path: Path to the first image file relative to working_dir image2_path: Path to the second image file relative to working_dir comparison_focus: What to focus on in the comparison (e.g., "colors", "objects", "composition", "similarities and differences") Returns: Detailed comparison of the two images """ try: # Analyze both images first using the cached describe_image function desc1 = describe_image(working_dir, image1_path, f"Describe this image focusing on {comparison_focus}.") if desc1.startswith("Error"): return f"Error with first image: {desc1}" desc2 = describe_image(working_dir, image2_path, f"Describe this image focusing on {comparison_focus}.") if desc2.startswith("Error"): return f"Error with second image: {desc2}" # Format comparison return f"Image Comparison - Focus: {comparison_focus}\n\n=== First Image ({image1_path}) ===\n{desc1}\n\n=== Second Image ({image2_path}) ===\n{desc2}" except Exception as e: return f"Error comparing images: {str(e)}" @mcp.tool() def get_image_metadata(working_dir: str, image_path: str) -> str: """ Get detailed metadata and technical information about an image file. Note: This function does not use caching as it reads file system info directly. Args: working_dir: Absolute path to the working directory for file operations image_path: Path to the image file relative to working_dir Returns: Technical metadata and information about the image """ try: # Validate working directory working_path = Path(working_dir) if not working_path.is_absolute(): return f"Error: working_dir must be an absolute path, got: {working_dir}" if not working_path.exists(): return f"Error: working_dir does not exist: {working_dir}" if not working_path.is_dir(): return f"Error: working_dir is not a directory: {working_dir}" # Validate image path with clear error messages is_valid, error_message, resolved_path = validate_image_path(image_path, "read", working_dir) if not is_valid: return error_message # Get comprehensive image info image_info = get_image_info(resolved_path) if "error" in image_info: return f"Error reading image: {image_info['error']}" # Get file stats file_stats = resolved_path.stat() metadata = f"""Image Metadata for '{image_path}': File Information: - File size: {file_stats.st_size:,} bytes ({file_stats.st_size / 1024 / 1024:.2f} MB) - Format: {image_info.get('format', 'Unknown')} - Dimensions: {image_info['size'][0]} x {image_info['size'][1]} pixels - Color mode: {image_info.get('mode', 'Unknown')} - Aspect ratio: {image_info['size'][0] / image_info['size'][1]:.2f} - Total pixels: {image_info['size'][0] * image_info['size'][1]:,} Path Information: - Absolute path: {resolved_path} - File extension: {resolved_path.suffix} - Parent directory: {resolved_path.parent}""" # Add EXIF data if available if 'exif' in image_info and image_info['exif']: metadata += f"\n\nEXIF Data Available: {len(image_info['exif'])} entries" # Show some common EXIF tags if present common_tags = ['DateTime', 'Make', 'Model', 'Software', 'ImageWidth', 'ImageLength'] for tag in common_tags: if tag in image_info['exif']: metadata += f"\n- {tag}: {image_info['exif'][tag]}" return metadata except Exception as e: return f"Error getting image metadata: {str(e)}" @mcp.tool() def get_cache_info() -> str: """ Get information about the image analysis cache. Returns: Cache statistics and information """ try: cache = get_cache() return cache.get_cache_info() except Exception as e: return f"Error getting cache info: {str(e)}" @mcp.tool() def clear_image_cache() -> str: """ Clear all cached image analysis results. Returns: Result of the cache clearing operation """ try: cache = get_cache() result = cache.clear_cache() return f"Cache cleared successfully. {result}" except Exception as e: return f"Error clearing cache: {str(e)}"

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/kareemaly/ai-image-mcp'

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