Skip to main content
Glama

gimp-mcp

gimp_mcp_server.py12.9 kB
#!/usr/bin/env python3 # GIMP MCP Server Script # Provides an MCP interface to control GIMP via a socket connection. from mcp.server.fastmcp import FastMCP, Context, Image # Adjust based on your MCP library import socket import json import logging import base64 import traceback logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger("GimpMCPServer") class GimpConnection: def __init__(self, host='localhost', port=9877): self.host = host self.port = port self.sock = None def connect(self): if self.sock: return try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.host, self.port)) logger.info(f"Connected to GIMP at {self.host}:{self.port}") except Exception as e: logger.error(f"Failed to connect: {e}") raise ConnectionError("Could not connect to GIMP. Ensure the MCP Server plugin is running.") def send_command(self, command_type, params=None): if not self.sock: self.connect() command = {"type": command_type, "params": params or {"args": []}} try: self.sock.sendall(json.dumps(command).encode('utf-8')) # Receive response in chunks for large data response_data = b'' while True: chunk = self.sock.recv(8192) if not chunk: break response_data += chunk # Try to parse as complete JSON try: json.loads(response_data.decode('utf-8')) break # Complete JSON received except (json.JSONDecodeError, UnicodeDecodeError): continue # Need more data self.sock = None return json.loads(response_data.decode('utf-8')) except Exception as e: logger.error(f"Communication error: {e}") self.sock = None raise Exception(f"Error communicating with GIMP: {e}") # Global connection _gimp_connection = None def get_gimp_connection(): global _gimp_connection if _gimp_connection is None: _gimp_connection = GimpConnection() _gimp_connection.connect() return _gimp_connection # MCP server mcp = FastMCP("GimpMCP", description="GIMP integration through MCP") @mcp.tool() def get_image_bitmap(ctx: Context, max_width: int | None = None, max_height: int | None = None, region: dict | None = None) -> Image: """Get the current open image in GIMP as an Image object with optional scaling and region selection. RECOMMENDED USAGE: - Use max_width=1024, max_height=1024 by default for optimal performance and manageable file sizes - Call get_image_metadata() first to understand image dimensions before extraction - Large images without scaling can result in very large data transfers Supports two main use cases: 1. Full image with optional scaling (pass max_width/max_height) 2. Region extraction with optional scaling (pass region dict) Parameters: - max_width, max_height: Target dimensions for scaling (center inside scaling) RECOMMENDED: Use 1024x1024 as default maximum for optimal performance - region: Dictionary with keys: - origin_x, origin_y: Top-left corner of region to extract - width, height: Dimensions of region to extract - max_width, max_height: Target dimensions for scaling extracted region (center inside scaling) Best Practice Workflow: 1. Call get_image_metadata() to get original image dimensions 2. Determine appropriate scaling or region based on image size 3. Call get_image_bitmap() with recommended max_width=1024, max_height=1024 Examples: - Recommended full image: get_image_bitmap(max_width=1024, max_height=1024) - Region: get_image_bitmap(region={"origin_x": 100, "origin_y": 50, "width": 400, "height": 300}) - Scaled region: get_image_bitmap(region={"origin_x": 100, "origin_y": 50, "width": 400, "height": 300, "max_width": 512, "max_height": 512}) Returns: - Image object containing PNG data in MCP-compliant format - Includes width, height, and base64-encoded image data The returned Image object automatically handles base64 encoding and MIME types according to the Model Context Protocol specification. Raises: - RuntimeError if no image is open, region is invalid, or export fails """ try: print("Requesting current image bitmap from GIMP...") conn = get_gimp_connection() # Build parameters for the bitmap request params = {} if max_width is not None: params["max_width"] = max_width if max_height is not None: params["max_height"] = max_height if region is not None: params["region"] = region result = conn.send_command("get_image_bitmap", params) if result["status"] == "success": # Extract the base64 image data image_info = result["results"] base64_data = image_info["image_data"] as_bytes = base64.b64decode(base64_data) # Return as MCP Image object (base64 data will be handled automatically) return Image(data=as_bytes, format="png") else: raise Exception(f"GIMP error: {result.get('error', 'Unknown error')}") except Exception as e: traceback.print_exc() raise Exception(f"Failed to get image bitmap: {e}") @mcp.tool() def get_image_metadata(ctx: Context) -> dict: """Get metadata about the current open image in GIMP without the bitmap data. Returns detailed information about the currently active image including: - Image dimensions (width, height) - Color mode and base type - Number of layers and channels - File information if available - Layer structure and properties This is much faster than get_image_bitmap() since it doesn't export the actual image data. Perfect for when you only need to know image properties for decision making. Returns: - Dictionary containing comprehensive image metadata - Raises exception if no images are open """ try: print("Requesting current image metadata from GIMP...") conn = get_gimp_connection() result = conn.send_command("get_image_metadata") if result["status"] == "success": return result["results"] else: raise Exception(f"GIMP error: {result.get('error', 'Unknown error')}") except Exception as e: traceback.print_exc() raise Exception(f"Failed to get image metadata: {e}") @mcp.tool() def get_gimp_info(ctx: Context) -> dict: """Get comprehensive information about the GIMP installation and environment. Returns detailed information about GIMP that AI assistants need to understand the current environment, including: - GIMP version and build information - Installation paths and directories - Available plugins and procedures - System configuration - Runtime environment details This information helps AI assistants provide better support and troubleshooting by understanding the specific GIMP setup they're working with. Returns: - Dictionary containing comprehensive GIMP environment information - Raises exception if GIMP connection fails """ try: print("Requesting GIMP environment information...") conn = get_gimp_connection() result = conn.send_command("get_gimp_info") if result["status"] == "success": return result["results"] else: raise Exception(f"GIMP error: {result.get('error', 'Unknown error')}") except Exception as e: traceback.print_exc() raise Exception(f"Failed to get GIMP info: {e}") @mcp.tool() def call_api(ctx: Context, api_path: str, args: list = [], kwargs: dict = {}) -> str: """Call GIMP 3.0 API methods through PyGObject console. GIMP MCP Protocol: - Use api_path="exec" to execute Python code in GIMP - args[0] should be "pyGObject-console" for executing commands - args[1] should be array of Python code strings to execute - Commands execute in persistent context - imports and variables persist - Always call Gimp.displays_flush() after drawing operations For image operations, use get_image_bitmap() which return proper MCP Image objects that Claude can process directly. Optional Initialization Pattern: ["images = Gimp.get_images()", "image1 = images[0]", "layers = image1.get_layers()", "layer1 = layers[0]", "drawable1 = layer1"] Common Operations: - Draw line: ["Gimp.pencil(drawable1, [0, 0, 200, 200])", "Gimp.displays_flush()"] - Set color: ["from gi.repository import Gegl", "red_color = Gegl.Color.new('red')", "Gimp.context_set_foreground(red_color)"] - Draw ellipse: ["Gimp.Image.select_ellipse(image1, Gimp.ChannelOps.REPLACE, 100, 100, 30, 20)", "Gimp.Drawable.edit_fill(drawable1, Gimp.FillType.FOREGROUND)", "Gimp.Selection.none(image1)", "Gimp.displays_flush()"] - Paint curve: ["Gimp.paintbrush_default(drawable1, [50.0, 50.0, 150.0, 200.0, 250.0, 50.0, 350.0, 200.0])", "Gimp.displays_flush()"] - Draw bezier curve: ["path = Gimp.Path.new(image1, 'my_bezier_path')", "image1.insert_path(path, None, 0)", "stroke_id = path.bezier_stroke_new_moveto(100, 100)", "path.bezier_stroke_cubicto(stroke_id, 150, 50, 250, 150, 300, 100)", "Gimp.Drawable.edit_stroke_item(drawable1, path)", "Gimp.Selection.none(image1)", "Gimp.displays_flush()"] - Get open filenames: ["print([x.get_file().get_path() for x in Gimp.get_images()])"] - Copy layer between images: ["image1 = Gimp.get_images()[0]", "image2 = Gimp.get_images()[1]", "width = image1.get_width()", "height = image1.get_height()", "image1.select_rectangle(Gimp.ChannelOps.REPLACE, 0, 0, width, height)", "image1_layers = image1.get_selected_layers()", "drawable = image1_layers[0]", "Gimp.edit_copy([drawable])", "image2_layers = image2.get_layers()", "target_drawable = image2_layers[0]", "floating_sel = Gimp.edit_paste(target_drawable, True)[0]", "Gimp.floating_sel_anchor(floating_sel)", "Gimp.displays_flush()"] - New image: ["image1 = Gimp.Image.new(350, 800, Gimp.ImageBaseType.RGB)", "layer1 = Gimp.Layer.new(image1, 'Background', 350, 800, Gimp.ImageType.RGB_IMAGE, 100, Gimp.LayerMode.NORMAL)", "image1.insert_layer(layer1, None, 0)", "drawable1 = layer1", "white_color = Gegl.Color.new('white')", "Gimp.context_set_background(white_color)", "Gimp.Drawable.edit_fill(drawable1, Gimp.FillType.BACKGROUND)", "Gimp.Display.new(image1)"] Important Tips: - When filling layers with color, ensure layer has alpha channel using Gimp.Layer.add_alpha() - Use Gimp.Drawable.fill() for reliable full-layer fills - Specify colors precisely with rgb(R, G, B) or rgba(R, G, B, A) to avoid transparency issues - After drawing operations, always call Gimp.displays_flush() - After selection operations for drawing, unselect with Gimp.Selection.none(image1) GIMP 3.0 API Changes: - Use Gimp.get_images() instead of deprecated Gimp.list_images() - Use image.get_layers() instead of Gimp.get_active_layer() - gimpfu module not available in GIMP 3.0 - Colors created with Gegl.Color.new('color_name') - Full API documentation: https://developer.gimp.org/api/3.0/libgimp/ Parameters: - api_path: Use "exec" for Python execution - args: ["pyGObject-console", ["python_code_array"]] or ["pyGObject-eval", ["expression"]] - kwargs: Dictionary of keyword arguments (rarely used) Returns: - JSON string of the result or error message """ try: conn = get_gimp_connection() result = conn.send_command("call_api", {"api_path": api_path, "args": args, "kwargs": kwargs}) if result["status"] == "success": return json.dumps(result["results"]) else: return f"Error: {json.dumps(result["error"])}" except Exception as e: return f"Error: {e}" def main(): mcp.run() if __name__ == "__main__": main()

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/maorcc/gimp-mcp'

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