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))
              }
            }
          };
        }
      }

Tool Definition Quality

Score is being calculated. Check back soon.

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