Skip to main content
Glama

create_image_edit

Edit existing images using text prompts to modify content, add elements, or change backgrounds with AI-powered image manipulation.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
imageYes
promptYes
backgroundNo
maskNo
nNo
qualityNo
sizeNo
userNo

Implementation Reference

  • Zod schema and TypeScript type for the create_image_edit tool parameters, supporting flexible image and mask inputs (base64 strings, arrays, or file paths).
    // Define the create_image_edit tool const createImageEditSchema = z.object({ image: z.union([ z.string(), // Can be base64 encoded image string z.array(z.string()), // Can be array of base64 encoded image strings z.object({ // Can be an object with a file path filePath: z.string(), isBase64: z.boolean().optional().default(false) }), z.array(z.object({ // Can be an array of objects with file paths filePath: z.string(), isBase64: z.boolean().optional().default(false) })) ]), prompt: z.string().max(32000, "Prompt exceeds maximum length for gpt-image-1."), background: z.enum(["transparent", "opaque", "auto"]).optional(), mask: z.union([ z.string(), // Can be base64 encoded mask string z.object({ // Can be an object with a file path filePath: z.string(), isBase64: z.boolean().optional().default(false) }) ]).optional(), n: z.number().int().min(1).max(10).optional(), quality: z.enum(["high", "medium", "low", "auto"]).optional(), size: z.enum(["1024x1024", "1536x1024", "1024x1536", "auto"]).optional(), user: z.string().optional() }); type CreateImageEditArgs = z.infer<typeof createImageEditSchema>;
  • src/index.ts:394-399 (registration)
    Registers the create_image_edit tool with the MCP server, providing name, schema, and description/title.
    server.tool( "create_image_edit", createImageEditSchema.shape, { title: "Edit existing images using OpenAI's gpt-image-1 model" },
  • Core handler function: Converts base64/file inputs to temp PNG files, executes curl to OpenAI /images/edits endpoint with gpt-image-1 model, parses JSON response, saves b64_json images via saveImageToDisk, cleans up temps, returns rich content with text summary, images, and metadata.
    async (args: CreateImageEditArgs, extra: any) => { try { // The OpenAI SDK expects 'image' and 'mask' to be Node.js ReadStream or Blob. // Since we are receiving base64 strings from the client, we need to convert them. // This is a simplified approach. A robust solution might involve handling file uploads // or different data formats depending on the client's capabilities. // For this implementation, we'll assume base64 and convert to Buffer, which the SDK might accept // or require further processing depending on its exact requirements for file-like objects. // NOTE: The OpenAI SDK's `images.edit` method specifically expects `File` or `Blob` in browser // environments and `ReadableStream` or `Buffer` in Node.js. Converting base64 to Buffer is // the most straightforward approach for a Node.js server receiving base64. // Process image input which can be file paths or base64 strings const imageFiles = []; // Handle different image input formats if (Array.isArray(args.image)) { // Handle array of strings or objects for (const img of args.image) { if (typeof img === 'string') { // Base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `image-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = img.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); imageFiles.push(tempFile); } else { // Object with filePath - use the file directly imageFiles.push(img.filePath); } } } else if (typeof args.image === 'string') { // Single base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `image-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = args.image.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); imageFiles.push(tempFile); } else { // Single object with filePath - use the file directly imageFiles.push(args.image.filePath); } // Process mask input which can be a file path or base64 string let maskFile = undefined; if (args.mask) { if (typeof args.mask === 'string') { // Mask is a base64 string - create a temporary file const tempFile = path.join(os.tmpdir(), `mask-${Date.now()}-${Math.random().toString(36).substring(2, 15)}.png`); const base64Data = args.mask.replace(/^data:image\/\w+;base64,/, ''); fs.writeFileSync(tempFile, Buffer.from(base64Data, 'base64')); maskFile = tempFile; } else { // Mask is an object with filePath - use the file directly maskFile = args.mask.filePath; } } // Use a direct curl command to call the OpenAI API // This is more reliable than using the SDK for file uploads // Create a temporary file to store the response const tempResponseFile = path.join(os.tmpdir(), `response-${Date.now()}.json`); // Build the curl command let curlCommand = `curl -s -X POST "https://api.openai.com/v1/images/edits" -H "Authorization: Bearer ${process.env.OPENAI_API_KEY}"`; // Add the model curlCommand += ` -F "model=gpt-image-1"`; // Add the prompt curlCommand += ` -F "prompt=${args.prompt}"`; // Add the images for (const imageFile of imageFiles) { curlCommand += ` -F "image[]=@${imageFile}"`; } // Add the mask if it exists if (maskFile) { curlCommand += ` -F "mask=@${maskFile}"`; } // Add other parameters if (args.n) curlCommand += ` -F "n=${args.n}"`; if (args.size) curlCommand += ` -F "size=${args.size}"`; if (args.quality) curlCommand += ` -F "quality=${args.quality}"`; if (args.background) curlCommand += ` -F "background=${args.background}"`; if (args.user) curlCommand += ` -F "user=${args.user}"`; // Add output redirection curlCommand += ` > "${tempResponseFile}"`; // Execute the curl command // Use execSync to run the curl command try { console.error(`Executing curl command to edit image...`); execSync(curlCommand, { stdio: ['pipe', 'pipe', 'inherit'] }); console.error(`Curl command executed successfully.`); } catch (error: any) { console.error(`Error executing curl command: ${error.message}`); throw new Error(`Failed to edit image: ${error.message}`); } // Read the response from the temporary file let responseJson; try { responseJson = fs.readFileSync(tempResponseFile, 'utf8'); console.error(`Response file read successfully.`); } catch (error: any) { console.error(`Error reading response file: ${error.message}`); throw new Error(`Failed to read response file: ${error.message}`); } // Parse the response let responseData; try { responseData = JSON.parse(responseJson); console.error(`Response parsed successfully.`); // Check if the response contains an error if (responseData.error) { console.error(`OpenAI API returned an error:`, responseData.error); const errorMessage = responseData.error.message || 'Unknown API error'; const errorType = responseData.error.type || 'api_error'; const errorCode = responseData.error.code || responseData.error.status || 'unknown'; throw { message: errorMessage, type: errorType, code: errorCode, response: { data: responseData } }; } } catch (error: any) { // If the error is from our API error check, rethrow it if (error.response && error.response.data) { throw error; } console.error(`Error parsing response: ${error.message}`); throw new Error(`Failed to parse response: ${error.message}`); } // Delete the temporary response file try { fs.unlinkSync(tempResponseFile); console.error(`Temporary response file deleted.`); } catch (error: any) { console.error(`Error deleting temporary file: ${error.message}`); // Don't throw an error here, just log it } // Clean up temporary files try { // Delete temporary image files for (const imageFile of imageFiles) { // Only delete files we created (temporary files in the os.tmpdir directory) if (imageFile.startsWith(os.tmpdir())) { try { fs.unlinkSync(imageFile); } catch (e) { /* ignore errors */ } } } // Delete temporary mask file if (maskFile && maskFile.startsWith(os.tmpdir())) { try { fs.unlinkSync(maskFile); } catch (e) { /* ignore errors */ } } } catch (cleanupError) { console.error("Error cleaning up temporary files:", cleanupError); } // No need for a Response-like object anymore since we're using fetch directly // Save images to disk and create response with file paths const savedImages = []; const imageContents = []; const format = "png"; // Assuming png for edits based on common practice if (responseData.data && responseData.data.length > 0) { for (const item of responseData.data) { if (item.b64_json) { // Save the image to disk const imagePath = saveImageToDisk(item.b64_json, format); // Add the saved image info to our response savedImages.push({ path: imagePath, format: format }); // Also include the image content for compatibility imageContents.push({ type: "image" as const, data: item.b64_json, mimeType: `image/${format}` }); } else if (item.url) { console.error(`Image URL: ${item.url}`); console.error("The gpt-image-1 model returned a URL instead of base64 data."); console.error("To view the image, open the URL in your browser."); // Add the URL info to our response savedImages.push({ url: item.url, format: format }); // Include a text message about the URL in the content imageContents.push({ type: "text" as const, text: `Image available at URL: ${item.url}` }); } } } // Create a beautifully formatted response with emojis and details const formatSize = (size: string | undefined) => size || "1024x1024"; const formatQuality = (quality: string | undefined) => quality || "high"; // Get source image information let sourceImageInfo = ""; if (Array.isArray(args.image)) { // Handle array of strings or objects sourceImageInfo = args.image.map((img, index) => { if (typeof img === 'string') { return ` ${index + 1}. Base64 encoded image`; } else { return ` ${index + 1}. ${img.filePath}`; } }).join('\n'); } else if (typeof args.image === 'string') { sourceImageInfo = " Base64 encoded image"; } else { sourceImageInfo = ` ${args.image.filePath}`; } // Get mask information let maskInfo = ""; if (args.mask) { if (typeof args.mask === 'string') { maskInfo = "šŸŽ­ **Mask**: Base64 encoded mask applied"; } else { maskInfo = `šŸŽ­ **Mask**: Mask from ${args.mask.filePath} applied`; } } // Create a beautiful formatted message const formattedMessage = ` āœļø **Image Edit Complete!** šŸ–Œļø ✨ **Edit Prompt**: "${args.prompt}" šŸ–¼ļø **Source Image${imageFiles.length > 1 ? 's' : ''}**: ${sourceImageInfo} ${maskInfo} šŸ“Š **Edit Parameters**: • Size: ${formatSize(args.size)} • Quality: ${formatQuality(args.quality)} • Number of Results: ${args.n || 1} ${args.background ? `• Background: ${args.background}` : ''} šŸ“ **Edited ${savedImages.length} Image${savedImages.length > 1 ? 's' : ''}**: ${savedImages.map((img, index) => ` ${index + 1}. ${img.path || img.url}`).join('\n')} ${responseData.usage ? `⚔ **Token Usage**: • Total Tokens: ${responseData.usage.total_tokens} • Input Tokens: ${responseData.usage.input_tokens} • Output Tokens: ${responseData.usage.output_tokens}` : ''} šŸ” You can find your edited image${savedImages.length > 1 ? 's' : ''} at the path${savedImages.length > 1 ? 's' : ''} above! `; // Return both the image content and the saved file paths with the beautiful message return { content: [ { type: "text" as const, text: formattedMessage }, ...imageContents ], ...(responseData.usage && { _meta: { usage: { totalTokens: responseData.usage.total_tokens, inputTokens: responseData.usage.input_tokens, outputTokens: responseData.usage.output_tokens, }, savedImages: savedImages } }) }; } catch (error: any) { // Log the full error for debugging console.error("Error creating image edit:", error); // Extract detailed error information const errorCode = error.status || error.code || 'Unknown'; const errorType = error.type || 'Error'; const errorMessage = error.message || 'An unknown error occurred'; // Check for specific error types and provide more helpful messages let detailedError = ''; let suggestedFix = ''; // Handle file-related errors if (errorMessage.includes('ENOENT') || errorMessage.includes('no such file')) { detailedError = '\nšŸ“‹ **Details**: The specified image or mask file could not be found'; suggestedFix = '\nšŸ’” **Suggestion**: Verify that the file path is correct and the file exists'; } // Handle permission errors else if (errorMessage.includes('EACCES') || errorMessage.includes('permission denied')) { detailedError = '\nšŸ“‹ **Details**: Permission denied when trying to access the file'; suggestedFix = '\nšŸ’” **Suggestion**: Check file permissions or try running with elevated privileges'; } // Handle curl errors else if (errorMessage.includes('curl')) { detailedError = '\nšŸ“‹ **Details**: Error occurred while sending the request to OpenAI API'; suggestedFix = '\nšŸ’” **Suggestion**: Check your internet connection and API key'; } // Handle OpenAI API errors else if (error.response) { try { const responseData = error.response.data || {}; if (responseData.error) { detailedError = `\nšŸ“‹ **Details**: ${responseData.error.message || 'No additional details available'}`; // Add parameter errors if available if (responseData.error.param) { detailedError += `\nšŸ” **Parameter**: ${responseData.error.param}`; } // Add code if available if (responseData.error.code) { detailedError += `\nšŸ”¢ **Error Code**: ${responseData.error.code}`; } // Add type if available if (responseData.error.type) { detailedError += `\nšŸ“ **Error Type**: ${responseData.error.type}`; } // Provide suggestions based on error type if (responseData.error.type === 'invalid_request_error') { suggestedFix = '\nšŸ’” **Suggestion**: Check that your image format is supported (PNG, JPEG) and the prompt is valid'; } else if (responseData.error.type === 'authentication_error') { suggestedFix = '\nšŸ’” **Suggestion**: Verify your OpenAI API key is correct and has access to the gpt-image-1 model'; } } } catch (parseError) { detailedError = '\nšŸ“‹ **Details**: Could not parse error details from API response'; } } // If we have a JSON response with an error, try to extract it if (errorMessage.includes('{') && errorMessage.includes('}')) { try { const jsonStartIndex = errorMessage.indexOf('{'); const jsonEndIndex = errorMessage.lastIndexOf('}') + 1; const jsonStr = errorMessage.substring(jsonStartIndex, jsonEndIndex); const jsonError = JSON.parse(jsonStr); if (jsonError.error) { detailedError = `\nšŸ“‹ **Details**: ${jsonError.error.message || 'No additional details available'}`; if (jsonError.error.code) { detailedError += `\nšŸ”¢ **Error Code**: ${jsonError.error.code}`; } if (jsonError.error.type) { detailedError += `\nšŸ“ **Error Type**: ${jsonError.error.type}`; } } } catch (e) { // If we can't parse JSON from the error message, just continue } } // Construct a comprehensive error message const fullErrorMessage = `āŒ **Image Edit Failed**\n\nāš ļø **Error ${errorCode}**: ${errorType} - ${errorMessage}${detailedError}${suggestedFix}\n\nšŸ”„ Please try again with a different prompt, image, or parameters.`; // Return the detailed error to the client return { content: [{ type: "text", text: fullErrorMessage }], isError: true, _meta: { error: { code: errorCode, type: errorType, message: errorMessage, details: detailedError.replace(/\nšŸ“‹ \*\*Details\*\*: /, ''), suggestion: suggestedFix.replace(/\nšŸ’” \*\*Suggestion\*\*: /, ''), raw: JSON.stringify(error, Object.getOwnPropertyNames(error)) } } }; } }

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/CLOUDWERX-DEV/gpt-image-1-mcp'

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