Skip to main content
Glama

edit_image

Modify existing images using text prompts and optional reference images. Change specific elements, apply styles, or adjust composition while maintaining the original file structure.

Instructions

Edit a SPECIFIC existing image file, optionally using additional reference images. Use this when you have the exact file path of an image to modify.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
aspectRatioNoOptional aspect ratio for the edited image. Default is 1:1 (1024Ɨ1024).
imagePathYesFull file path to the main image file to edit
promptYesText describing the modifications to make to the existing image
referenceImagesNoOptional array of file paths to additional reference images to use during editing (e.g., for style transfer, adding elements, etc.)

Implementation Reference

  • The primary handler function that implements the 'edit_image' tool logic. It reads the input image, optionally reference images, constructs a prompt for the OpenRouter Gemini model, generates the edited image, saves it to disk, and returns the result with embedded image data.
    private async editImage(request: CallToolRequest): Promise<CallToolResult> {
      if (!this.ensureConfigured()) {
        throw new McpError(ErrorCode.InvalidRequest, "OpenRouter API token not configured. Use configure_openrouter_token first.");
      }
    
      const { imagePath, prompt, referenceImages, aspectRatio } = request.params.arguments as { 
        imagePath: string; 
        prompt: string; 
        referenceImages?: string[];
        aspectRatio?: AspectRatio;
      };
      
      try {
        // Prepare the main image
        const imageBuffer = await fs.readFile(imagePath);
        const mimeType = this.getMimeType(imagePath);
        const imageBase64 = imageBuffer.toString('base64');
        
        // Build content array for the message
        const contentParts: any[] = [];
        
        // Add the main image
        contentParts.push({
          type: "image_url",
          image_url: {
            url: `data:${mimeType};base64,${imageBase64}`,
          }
        });
        
        // Add reference images if provided
        if (referenceImages && referenceImages.length > 0) {
          for (const refPath of referenceImages) {
            try {
              const refBuffer = await fs.readFile(refPath);
              const refMimeType = this.getMimeType(refPath);
              const refBase64 = refBuffer.toString('base64');
              
              contentParts.push({
                type: "image_url",
                image_url: {
                  url: `data:${refMimeType};base64,${refBase64}`,
                }
              });
            } catch (error) {
              // Continue with other images, don't fail the entire operation
              continue;
            }
          }
        }
        
        // Add the text prompt
        contentParts.push({
          type: "text",
          text: prompt,
        });
        
        // Prepare the request payload
        const requestPayload: any = {
          model: "google/gemini-2.5-flash-image",
          messages: [
            {
              role: "user",
              content: contentParts,
            }
          ],
          modalities: ["image", "text"],
        };
    
        // Add aspect ratio if provided
        if (aspectRatio) {
          requestPayload.image_config = {
            aspect_ratio: aspectRatio,
          };
        }
    
        const response = await this.openai!.chat.completions.create(requestPayload);
        
        // Process response
        const content: any[] = [];
        const savedFiles: string[] = [];
        let textContent = "";
        
        // Get appropriate save directory
        const imagesDir = this.getImagesDirectory();
        await fs.mkdir(imagesDir, { recursive: true, mode: 0o755 });
        
        const message = response.choices[0]?.message;
        
        if (message) {
          // Process text content
          if (message.content && typeof message.content === 'string') {
            textContent = message.content;
          }
          
          // Process images
          if ((message as any).images && Array.isArray((message as any).images)) {
            for (const imageData of (message as any).images) {
              const imageUrl = imageData.image_url?.url;
              
              if (imageUrl && imageUrl.startsWith('data:image/')) {
                // Extract base64 data from data URL
                const matches = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/);
                
                if (matches && matches[2]) {
                  const base64Data = matches[2];
                  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
                  const randomId = Math.random().toString(36).substring(2, 8);
                  const fileName = `edited-${timestamp}-${randomId}.png`;
                  const filePath = path.join(imagesDir, fileName);
                  
                  const imageBuffer = Buffer.from(base64Data, 'base64');
                  await fs.writeFile(filePath, imageBuffer);
                  savedFiles.push(filePath);
                  this.lastImagePath = filePath;
                  
                  // Add image to MCP response
                  content.push({
                    type: "image",
                    data: base64Data,
                    mimeType: "image/png",
                  });
                }
              }
            }
          }
        }
        
        // Build response
        let statusText = `šŸŽØ Image edited with OpenRouter!\n\nOriginal: ${imagePath}\nEdit prompt: "${prompt}"`;
        
        if (aspectRatio) {
          statusText += `\nAspect Ratio: ${aspectRatio}`;
        }
        
        if (referenceImages && referenceImages.length > 0) {
          statusText += `\n\nReference images used:\n${referenceImages.map(f => `- ${f}`).join('\n')}`;
        }
        
        if (textContent) {
          statusText += `\n\nDescription: ${textContent}`;
        }
        
        if (savedFiles.length > 0) {
          statusText += `\n\nšŸ“ Edited image saved to:\n${savedFiles.map(f => `- ${f}`).join('\n')}`;
          statusText += `\n\nšŸ’” View the edited image by:`;
          statusText += `\n1. Opening the file at the path above`;
          statusText += `\n2. Clicking on "Called edit_image" in Cursor to expand the MCP call details`;
          statusText += `\n\nšŸ”„ To continue editing, use: continue_editing`;
          statusText += `\nšŸ“‹ To check current image info, use: get_last_image_info`;
        } else {
          statusText += `\n\nNote: No edited image was generated.`;
          statusText += `\n\nšŸ’” Tip: Try running the command again - sometimes the first call needs to warm up the model.`;
        }
        
        content.unshift({
          type: "text",
          text: statusText,
        });
        
        return { content };
        
      } catch (error) {
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to edit image: ${error instanceof Error ? error.message : String(error)}`
        );
      }
    }
  • JSON schema defining the input parameters for the 'edit_image' tool, including required imagePath and prompt, optional referenceImages and aspectRatio.
    inputSchema: {
      type: "object",
      properties: {
        imagePath: {
          type: "string",
          description: "Full file path to the main image file to edit",
        },
        prompt: {
          type: "string",
          description: "Text describing the modifications to make to the existing image",
        },
        referenceImages: {
          type: "array",
          items: {
            type: "string"
          },
          description: "Optional array of file paths to additional reference images to use during editing (e.g., for style transfer, adding elements, etc.)",
        },
        aspectRatio: {
          type: "string",
          enum: ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
          description: "Optional aspect ratio for the edited image. Default is 1:1 (1024Ɨ1024).",
        },
      },
      required: ["imagePath", "prompt"],
  • src/index.ts:97-126 (registration)
    Registration of the 'edit_image' tool in the ListToolsRequestSchema handler, providing name, description, and input schema.
    {
      name: "edit_image",
      description: "Edit a SPECIFIC existing image file, optionally using additional reference images. Use this when you have the exact file path of an image to modify.",
      inputSchema: {
        type: "object",
        properties: {
          imagePath: {
            type: "string",
            description: "Full file path to the main image file to edit",
          },
          prompt: {
            type: "string",
            description: "Text describing the modifications to make to the existing image",
          },
          referenceImages: {
            type: "array",
            items: {
              type: "string"
            },
            description: "Optional array of file paths to additional reference images to use during editing (e.g., for style transfer, adding elements, etc.)",
          },
          aspectRatio: {
            type: "string",
            enum: ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"],
            description: "Optional aspect ratio for the edited image. Default is 1:1 (1024Ɨ1024).",
          },
        },
        required: ["imagePath", "prompt"],
      },
    },
  • src/index.ts:184-185 (registration)
    Dispatch registration in the CallToolRequestSchema switch statement that routes calls to the editImage handler method.
    case "edit_image":
      return await this.editImage(request);
  • Helper usage of the editImage handler within the continue_editing tool to reuse the editing logic with the last image path.
    return await this.editImage({
      method: "tools/call",
      params: {
        name: "edit_image",
        arguments: {
          imagePath: this.lastImagePath,
          prompt: prompt,
          referenceImages: referenceImages,
          aspectRatio: aspectRatio
        }
      }
    } as CallToolRequest);
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the tool edits an existing image but doesn't disclose critical behavioral traits: whether this is a destructive mutation (overwrites the original or creates a new file), what permissions or authentication are needed, any rate limits, error handling, or the nature of the output (e.g., returns a new file path or modifies in-place). For a mutation tool with zero annotation coverage, this is a significant gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded: two concise sentences that directly state the purpose and usage context. Every sentence earns its place by providing essential information without redundancy or fluff, making it efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of an image editing tool with no annotations and no output schema, the description is incomplete. It lacks behavioral details (e.g., mutation effects, auth needs), doesn't explain the return values or output format, and relies heavily on the schema for parameters. For a tool that performs edits, this leaves significant gaps in understanding how to use it effectively.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all four parameters (imagePath, prompt, aspectRatio, referenceImages) with descriptions. The description adds minimal value beyond the schema: it mentions 'optionally using additional reference images' (implied by referenceImages) and 'exact file path' (implied by imagePath). No additional syntax, format details, or constraints are provided, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Edit a SPECIFIC existing image file, optionally using additional reference images.' It specifies the verb ('edit'), resource ('existing image file'), and scope ('specific'), distinguishing it from sibling tools like 'generate_image' (creation) and 'continue_editing' (ongoing edits). However, it doesn't explicitly differentiate from all siblings (e.g., 'get_last_image_info' is informational, but this isn't mentioned).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use this tool: 'Use this when you have the exact file path of an image to modify.' This gives a specific prerequisite (file path availability) and implies it's for modifying existing images rather than creating new ones. It doesn't explicitly state when not to use it or name alternatives like 'generate_image', but the context is sufficient for basic guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/WeiYu021/openrouter-image-MCP'

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