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
| Name | Required | Description | Default |
|---|---|---|---|
| aspectRatio | No | Optional aspect ratio for the edited image. Default is 1:1 (1024×1024). | |
| imagePath | Yes | Full file path to the main image file to edit | |
| prompt | Yes | Text describing the modifications to make to the existing image | |
| referenceImages | No | Optional array of file paths to additional reference images to use during editing (e.g., for style transfer, adding elements, etc.) |
Implementation Reference
- src/index.ts:363-530 (handler)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)}` ); } }
- src/index.ts:100-124 (schema)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);
- src/index.ts:598-609 (helper)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);