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

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