AiDD MCP Server

import base64 import io import os from mcp.types import TextContent from PIL import Image from .state import state # Maximum file size (100MB) MAX_FILE_SIZE = 100 * 1024 * 1024 # Image size constraints MIN_WIDTH = 20 MAX_WIDTH = 800 def read_image_file_tool(): return { "name": "read_image_file", "description": "Read an image file from the file system and return its contents as a base64-encoded string. " "This tool supports common image formats like PNG, JPEG, GIF, and WebP. " "The image data is returned in a format that can be displayed or processed by other tools. " "Only works within the allowed directory. " "Maximum file size is 100MB. " "Example: Enter 'images/logo.png' to read a PNG image file.", "inputSchema": { "type": "object", "properties": { "path": { "type": "string", "description": "Path to the image file to read" }, "max_size": { "type": "integer", "description": "Maximum file size in bytes (default: 100MB)", "optional": True } }, "required": ["path"] }, } async def handle_read_image_file(arguments: dict): """Handle reading an image file and converting it to base64.""" path = arguments.get("path") max_size = arguments.get("max_size", MAX_FILE_SIZE) if not path: raise ValueError("path must be provided") # Determine full path based on whether input is absolute or relative if os.path.isabs(path): full_path = os.path.abspath(path) # Just normalize the absolute path else: # For relative paths, join with allowed_directory full_path = os.path.abspath(os.path.join(state.allowed_directory, path)) if not full_path.startswith(state.allowed_directory): raise ValueError(f"Access denied: Path ({full_path}) must be within allowed directory ({state.allowed_directory})") if not os.path.exists(full_path): raise ValueError(f"File does not exist: {full_path}") if not os.path.isfile(full_path): raise ValueError(f"Path is not a file: {full_path}") # Check file size before attempting to read file_size = os.path.getsize(full_path) if file_size > max_size: raise ValueError(f"File size ({file_size} bytes) exceeds maximum allowed size ({max_size} bytes)") try: # Try to open the image with PIL to validate it's a valid image with Image.open(full_path) as img: # Get the image format image_format = img.format.lower() if not image_format: # Try to determine format from file extension ext = os.path.splitext(full_path)[1].lower().lstrip('.') if ext in ['jpg', 'jpeg']: image_format = 'jpeg' elif ext in ['png', 'gif', 'webp']: image_format = ext else: raise ValueError(f"Unsupported image format: {ext}") # Resize image if width is greater than MAX_WIDTH or less than MIN_WIDTH if img.width > MAX_WIDTH or img.width < MIN_WIDTH: # Calculate new dimensions maintaining aspect ratio if img.width > MAX_WIDTH: target_width = MAX_WIDTH else: target_width = MIN_WIDTH ratio = target_width / img.width new_height = int(img.height * ratio) img = img.resize((target_width, new_height), Image.Resampling.LANCZOS) # Convert image to bytes img_byte_arr = io.BytesIO() if image_format.lower() == 'jpeg': img.save(img_byte_arr, format=image_format, quality=85) # Specify quality for JPEG else: img.save(img_byte_arr, format=image_format) img_byte_arr = img_byte_arr.getvalue() # Convert to base64 base64_data = base64.b64encode(img_byte_arr).decode('utf-8') # Return the image data with its type return [TextContent( type="text", text=f"data:image/{image_format};base64,{base64_data}" )] except Image.UnidentifiedImageError: raise ValueError(f"File is not a valid image: {path}") except Exception as e: raise ValueError(f"Error reading image file: {str(e)}")