Skip to main content
Glama

generate_and_upload_image

Generate and upload custom images using AI by providing a text prompt, file name, and optional parameters like dimensions, aspect ratio, and output format. Integrates with Printify MCP Server for print-on-demand product design.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
aspectRatioNoAspect ratio (e.g., '16:9', '4:3', '1:1'). If provided, overrides width and height
fileNameYesFile name for the uploaded image
guidanceScaleNoGuidance scale
heightNoImage height in pixels
imagePromptStrengthNoImage prompt strength 0-1 (Flux 1.1 Pro Ultra only)
modelNoOptional: Override the default model. Use get_defaults to see available models
negativePromptNoNegative promptlow quality, bad quality, sketches
numInferenceStepsNoNumber of inference steps
outputFormatNoOutput formatpng
outputQualityNoOutput quality 1-100 (Flux 1.1 Pro only)
promptYesText prompt for image generation
promptUpsamplingNoEnable prompt upsampling (Flux 1.1 Pro only)
rawNoGenerate less processed, more natural-looking images (Flux 1.1 Pro Ultra only)
safetyToleranceNoSafety tolerance (0-6)
seedNoRandom seed for reproducible generation
widthNoImage width in pixels

Implementation Reference

  • src/index.ts:859-1244 (registration)
    Tool registration including schema and inline handler function for generate_and_upload_image
    // Generate and upload image tool - combines Replicate image generation with Printify upload
    server.tool(
      "generate_and_upload_image",
      {
        prompt: z.string().describe("Text prompt for image generation"),
        fileName: z.string().describe("File name for the uploaded image"),
    
        // Optional model override
        model: z.string().optional()
          .describe("Optional: Override the default model. Use get_defaults to see available models"),
    
        // Common parameters for both models
        width: z.number().optional().default(1024).describe("Image width in pixels"),
        height: z.number().optional().default(1024).describe("Image height in pixels"),
        aspectRatio: z.string().optional().describe("Aspect ratio (e.g., '16:9', '4:3', '1:1'). If provided, overrides width and height"),
        outputFormat: z.enum(["jpeg", "png", "webp"]).optional().default("png").describe("Output format"),
        safetyTolerance: z.number().optional().default(2).describe("Safety tolerance (0-6)"),
        seed: z.number().optional().describe("Random seed for reproducible generation"),
        numInferenceSteps: z.number().optional().default(25).describe("Number of inference steps"),
        guidanceScale: z.number().optional().default(7.5).describe("Guidance scale"),
        negativePrompt: z.string().optional().default("low quality, bad quality, sketches").describe("Negative prompt"),
    
        // Flux 1.1 Pro specific parameters
        promptUpsampling: z.boolean().optional()
          .describe("Enable prompt upsampling (Flux 1.1 Pro only)"),
        outputQuality: z.number().optional()
          .describe("Output quality 1-100 (Flux 1.1 Pro only)"),
    
        // Flux 1.1 Pro Ultra specific parameters
        raw: z.boolean().optional()
          .describe("Generate less processed, more natural-looking images (Flux 1.1 Pro Ultra only)"),
        imagePromptStrength: z.number().optional()
          .describe("Image prompt strength 0-1 (Flux 1.1 Pro Ultra only)")
      },
      async ({
        prompt, fileName, model, width, height, aspectRatio, outputFormat, safetyTolerance,
        seed, numInferenceSteps, guidanceScale, negativePrompt, promptUpsampling, outputQuality,
        raw, imagePromptStrength
      }): Promise<{ content: any[], isError?: boolean }> => {
        // Import the services
        const { generateImage } = await import('./services/image-generator.js');
        const { formatSuccessResponse } = await import('./utils/error-handler.js');
    
        // Check if clients are initialized
        if (!replicateClient) {
          return {
            content: [{
              type: "text",
              text: "Replicate API client is not initialized. The REPLICATE_API_TOKEN environment variable may not be set."
            }],
            isError: true
          };
        }
    
        if (!printifyClient) {
          return {
            content: [{
              type: "text",
              text: "Printify API client is not initialized. The PRINTIFY_API_KEY environment variable may not be set."
            }],
            isError: true
          };
        }
    
        // Check if we're using the Ultra model which requires ImgBB
        // Determine which model to use (user-specified or default)
        const modelToUse = model || replicateClient.getDefaultModel();
    
        // Check if ImgBB API key is set when using Ultra model
        if (modelToUse.includes('flux-1.1-pro-ultra') && (!process.env.IMGBB_API_KEY || process.env.IMGBB_API_KEY === 'your-imgbb-api-key')) {
          return {
            content: [{
              type: "text",
              text: `ERROR: The Flux 1.1 Pro Ultra model generates high-resolution images that are too large for direct base64 upload.\n\n` +
                    `You MUST set the IMGBB_API_KEY environment variable when using this model.\n\n` +
                    `Get a free API key from https://api.imgbb.com/ and add it to your .env file:\n` +
                    `IMGBB_API_KEY=your_api_key_here`
            }],
            isError: true
          };
        }
    
        // Check if a shop is selected
        const currentShop = printifyClient.getCurrentShop();
        if (!currentShop) {
          return {
            content: [{
              type: "text",
              text: "No shop is currently selected. Use the list_shops and switch_shop tools to select a shop."
            }],
            isError: true
          };
        }
    
        console.log(`Starting generate_and_upload_image with prompt: ${prompt}`);
    
        // Get default parameters first
        const defaults = replicateClient.getAllDefaults();
    
        // STEP 1: Generate the image with Replicate and process with Sharp
        // Start with defaults, then override with parameters from the tool call
        const generationResult = await generateImage(
          replicateClient,
          prompt,
          fileName,
          {
            // Start with defaults
            model: defaults.model,
            width: defaults.width,
            height: defaults.height,
            aspectRatio: defaults.aspectRatio,
            outputFormat: defaults.outputFormat,
            safetyTolerance: defaults.safetyTolerance,
            numInferenceSteps: defaults.numInferenceSteps,
            guidanceScale: defaults.guidanceScale,
            negativePrompt: defaults.negativePrompt,
            raw: defaults.raw,
            promptUpsampling: defaults.promptUpsampling,
            outputQuality: defaults.outputQuality,
    
            // Override with parameters from the tool call (if provided)
            ...(model !== undefined && { model }),
            ...(width !== undefined && { width }),
            ...(height !== undefined && { height }),
            ...(aspectRatio !== undefined && { aspectRatio }),
            ...(outputFormat !== undefined && { outputFormat }),
            ...(safetyTolerance !== undefined && { safetyTolerance }),
            ...(seed !== undefined && { seed }),
            ...(numInferenceSteps !== undefined && { numInferenceSteps }),
            ...(guidanceScale !== undefined && { guidanceScale }),
            ...(negativePrompt !== undefined && { negativePrompt }),
            ...(promptUpsampling !== undefined && { promptUpsampling }),
            ...(outputQuality !== undefined && { outputQuality }),
            ...(raw !== undefined && { raw }),
            ...(imagePromptStrength !== undefined && { imagePromptStrength })
          }
        );
    
        // If image generation failed, return the error
        if (!generationResult.success) {
          return generationResult.errorResponse as { content: any[], isError: boolean };
        }
    
        const imageBuffer = generationResult.buffer;
        const mimeType = generationResult.mimeType;
        const finalFileName = generationResult.fileName;
        const usingModel = generationResult.model;
    
        // Make sure we have valid image data
        if (!imageBuffer) {
          return {
            content: [{
              type: "text",
              text: "Failed to get valid image data from the image generator."
            }],
            isError: true
          };
        }
    
        // STEP 2: Upload the processed image to Printify
        console.log(`Uploading processed image to Printify`);
        console.log(`Image buffer size: ${imageBuffer.length} bytes`);
        console.log(`MIME type: ${mimeType}`);
        console.log(`File name: ${finalFileName}`);
    
        // Prepare for upload to Printify
        const uploadDetails = [
          `Preparing to upload image to Printify:`,
          `- File name: ${finalFileName}`,
          `- Image buffer size: ${imageBuffer?.length || 0} bytes`,
          `- MIME type: ${mimeType}`,
          `- Model used: ${usingModel}`
        ].join('\n');
    
        // Save the base64 data to a debug file for inspection
        try {
          const fs = await import('fs');
          const path = await import('path');
    
          // Create a debug directory if it doesn't exist
          const debugDir = path.join(process.cwd(), 'debug');
          if (!fs.existsSync(debugDir)) {
            fs.mkdirSync(debugDir, { recursive: true });
          }
    
          // Save the base64 data to a file for debugging
          const debugFilePath = path.join(debugDir, `debug_${Date.now()}_${finalFileName}`);
    
          // Save buffer directly to debug file
          if (imageBuffer) {
            fs.writeFileSync(debugFilePath, imageBuffer);
            console.log(`Saved image data to debug file: ${debugFilePath}`);
            console.log(`Debug file size: ${imageBuffer.length} bytes`);
          } else {
            console.error('No image data to save for debugging');
          }
        } catch (debugError) {
          console.error('Error saving debug file:', debugError);
        }
    
        // Validate input data
        if (!imageBuffer) {
          return {
            content: [{
              type: "text",
              text: "Error: No image data available for upload"
            }],
            isError: true
          };
        }
    
        if (!finalFileName) {
          return {
            content: [{
              type: "text",
              text: "Error: No filename available for upload"
            }],
            isError: true
          };
        }
    
        // STEP 1: Import required modules
        let axios;
        let FormData;
        try {
          axios = (await import('axios')).default;
          FormData = (await import('form-data')).default;
        } catch (importError: any) {
          return {
            content: [{
              type: "text",
              text: `Error importing required modules: ${importError.message || String(importError)}`
            }],
            isError: true
          };
        }
    
        // STEP 2: Prepare image for upload (either via ImgBB or direct base64)
        let imageUrl;
        let uploadMethod = "direct";
    
        // Check if we're using the Ultra model
        const isUsingUltraModel = usingModel.includes('flux-1.1-pro-ultra');
    
        // Check if ImgBB API key is set
        const imgbbApiKey = process.env.IMGBB_API_KEY;
    
        if (imgbbApiKey && imgbbApiKey !== 'your-imgbb-api-key') {
          // If ImgBB API key is set, use ImgBB to get a URL
          try {
            // Create form data for ImgBB
            const formData = new FormData();
            // Convert buffer to base64 for ImgBB upload
            const base64Data = imageBuffer.toString('base64');
            formData.append('image', base64Data);
    
            // Upload to ImgBB with the key as a query parameter
            const imgbbResponse = await axios.post(
              `https://api.imgbb.com/1/upload?key=${imgbbApiKey}`,
              formData
            );
    
            // Get the image URL from ImgBB response
            imageUrl = imgbbResponse.data.data.url;
            uploadMethod = "imgbb";
    
            // Log success
            console.log(`Successfully uploaded image to ImgBB. URL: ${imageUrl}`);
          } catch (imgbbError: any) {
            // Only fall back to direct upload if not using Ultra model
            if (isUsingUltraModel) {
              return {
                content: [{
                  type: "text",
                  text: `Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}\n\n` +
                        `When using the Ultra model, ImgBB upload is required and cannot be bypassed.\n\n` +
                        `Response data: ${JSON.stringify(imgbbError.response?.data || {}, null, 2)}`
                }],
                isError: true
              };
            }
    
            console.log(`Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}. Falling back to direct base64 upload.`);
            // Fall back to direct base64 upload for non-Ultra models
            uploadMethod = "direct";
          }
        } else if (!isUsingUltraModel) {
          console.log("No ImgBB API key found. Using direct base64 upload.");
        }
    
        // STEP 4: Import Printify SDK
        let Printify;
        try {
          Printify = (await import('printify-sdk-js')).default;
        } catch (importError: any) {
          return {
            content: [{
              type: "text",
              text: `Error importing Printify SDK: ${importError.message || String(importError)}\n\n` +
                    `ImgBB URL: ${imageUrl}`
            }],
            isError: true
          };
        }
    
        // STEP 5: Create Printify client
        let printifySDK;
        try {
          printifySDK = new Printify({
            accessToken: process.env.PRINTIFY_API_KEY || '',
            shopId: printifyClient ? printifyClient.getCurrentShopId() || undefined : undefined
          });
    
          // Log client creation
          console.log(`Created Printify client with shop ID: ${printifyClient?.getCurrentShopId() || 'undefined'}`);
        } catch (clientError: any) {
          return {
            content: [{
              type: "text",
              text: `Error creating Printify client: ${clientError.message || String(clientError)}\n\n` +
                    `ImgBB URL: ${imageUrl}`
            }],
            isError: true
          };
        }
    
        // STEP 6: Upload the image to Printify
        let image;
        try {
          if (uploadMethod === "imgbb" && imageUrl) {
            // Upload using the URL from ImgBB
            image = await printifySDK.uploads.uploadImage({
              file_name: finalFileName,
              url: imageUrl
            });
            console.log(`Successfully uploaded image to Printify using ImgBB URL. Image ID: ${image.id}`);
          } else {
            // Direct base64 upload
            // Convert buffer to base64 for Printify direct upload
            const base64Data = imageBuffer.toString('base64');
            image = await printifySDK.uploads.uploadImage({
              file_name: finalFileName,
              contents: base64Data
            });
            console.log(`Successfully uploaded image to Printify using direct base64. Image ID: ${image.id}`);
          }
        } catch (uploadError: any) {
          return {
            content: [{
              type: "text",
              text: `Error uploading to Printify: ${uploadError.message || String(uploadError)}\n\n` +
                    `Upload method: ${uploadMethod}${imageUrl ? `\nImgBB URL: ${imageUrl}` : ''}\n\n` +
                    `Response data: ${JSON.stringify(uploadError.response?.data || {}, null, 2)}`
            }],
            isError: true
          };
        }
    
        // STEP 7: Return success response
        const response = formatSuccessResponse(
          'Image Generated and Uploaded Successfully',
          {
            Prompt: prompt,
            Model: usingModel.split('/')[1],
            'Image ID': image.id,
            'File Name': image.file_name,
            Dimensions: `${image.width}x${image.height}`,
            'Preview URL': image.preview_url,
            'Upload Method': uploadMethod === "imgbb" ? "ImgBB URL" : "Direct base64",
            ...(imageUrl ? { 'ImgBB URL': imageUrl } : {}),
            'Upload Details': uploadDetails
          },
          `You can now use this image ID (${image.id}) when creating a product.\n\n` +
          `**Example:**\n` +
          `\`\`\`json\n` +
          `"print_areas": {\n` +
          `  "front": { "position": "front", "imageId": "${image.id}" }\n` +
          `}\n` +
          `\`\`\``
        ) as { content: any[], isError?: boolean };
    
        return response;
    
    
      }
    );
  • The core handler function that orchestrates image generation using Replicate (via generateImage helper) and uploads the resulting image to Printify, handling model-specific logic and upload methods (direct base64 or via ImgBB for large images).
    async ({
      prompt, fileName, model, width, height, aspectRatio, outputFormat, safetyTolerance,
      seed, numInferenceSteps, guidanceScale, negativePrompt, promptUpsampling, outputQuality,
      raw, imagePromptStrength
    }): Promise<{ content: any[], isError?: boolean }> => {
      // Import the services
      const { generateImage } = await import('./services/image-generator.js');
      const { formatSuccessResponse } = await import('./utils/error-handler.js');
    
      // Check if clients are initialized
      if (!replicateClient) {
        return {
          content: [{
            type: "text",
            text: "Replicate API client is not initialized. The REPLICATE_API_TOKEN environment variable may not be set."
          }],
          isError: true
        };
      }
    
      if (!printifyClient) {
        return {
          content: [{
            type: "text",
            text: "Printify API client is not initialized. The PRINTIFY_API_KEY environment variable may not be set."
          }],
          isError: true
        };
      }
    
      // Check if we're using the Ultra model which requires ImgBB
      // Determine which model to use (user-specified or default)
      const modelToUse = model || replicateClient.getDefaultModel();
    
      // Check if ImgBB API key is set when using Ultra model
      if (modelToUse.includes('flux-1.1-pro-ultra') && (!process.env.IMGBB_API_KEY || process.env.IMGBB_API_KEY === 'your-imgbb-api-key')) {
        return {
          content: [{
            type: "text",
            text: `ERROR: The Flux 1.1 Pro Ultra model generates high-resolution images that are too large for direct base64 upload.\n\n` +
                  `You MUST set the IMGBB_API_KEY environment variable when using this model.\n\n` +
                  `Get a free API key from https://api.imgbb.com/ and add it to your .env file:\n` +
                  `IMGBB_API_KEY=your_api_key_here`
          }],
          isError: true
        };
      }
    
      // Check if a shop is selected
      const currentShop = printifyClient.getCurrentShop();
      if (!currentShop) {
        return {
          content: [{
            type: "text",
            text: "No shop is currently selected. Use the list_shops and switch_shop tools to select a shop."
          }],
          isError: true
        };
      }
    
      console.log(`Starting generate_and_upload_image with prompt: ${prompt}`);
    
      // Get default parameters first
      const defaults = replicateClient.getAllDefaults();
    
      // STEP 1: Generate the image with Replicate and process with Sharp
      // Start with defaults, then override with parameters from the tool call
      const generationResult = await generateImage(
        replicateClient,
        prompt,
        fileName,
        {
          // Start with defaults
          model: defaults.model,
          width: defaults.width,
          height: defaults.height,
          aspectRatio: defaults.aspectRatio,
          outputFormat: defaults.outputFormat,
          safetyTolerance: defaults.safetyTolerance,
          numInferenceSteps: defaults.numInferenceSteps,
          guidanceScale: defaults.guidanceScale,
          negativePrompt: defaults.negativePrompt,
          raw: defaults.raw,
          promptUpsampling: defaults.promptUpsampling,
          outputQuality: defaults.outputQuality,
    
          // Override with parameters from the tool call (if provided)
          ...(model !== undefined && { model }),
          ...(width !== undefined && { width }),
          ...(height !== undefined && { height }),
          ...(aspectRatio !== undefined && { aspectRatio }),
          ...(outputFormat !== undefined && { outputFormat }),
          ...(safetyTolerance !== undefined && { safetyTolerance }),
          ...(seed !== undefined && { seed }),
          ...(numInferenceSteps !== undefined && { numInferenceSteps }),
          ...(guidanceScale !== undefined && { guidanceScale }),
          ...(negativePrompt !== undefined && { negativePrompt }),
          ...(promptUpsampling !== undefined && { promptUpsampling }),
          ...(outputQuality !== undefined && { outputQuality }),
          ...(raw !== undefined && { raw }),
          ...(imagePromptStrength !== undefined && { imagePromptStrength })
        }
      );
    
      // If image generation failed, return the error
      if (!generationResult.success) {
        return generationResult.errorResponse as { content: any[], isError: boolean };
      }
    
      const imageBuffer = generationResult.buffer;
      const mimeType = generationResult.mimeType;
      const finalFileName = generationResult.fileName;
      const usingModel = generationResult.model;
    
      // Make sure we have valid image data
      if (!imageBuffer) {
        return {
          content: [{
            type: "text",
            text: "Failed to get valid image data from the image generator."
          }],
          isError: true
        };
      }
    
      // STEP 2: Upload the processed image to Printify
      console.log(`Uploading processed image to Printify`);
      console.log(`Image buffer size: ${imageBuffer.length} bytes`);
      console.log(`MIME type: ${mimeType}`);
      console.log(`File name: ${finalFileName}`);
    
      // Prepare for upload to Printify
      const uploadDetails = [
        `Preparing to upload image to Printify:`,
        `- File name: ${finalFileName}`,
        `- Image buffer size: ${imageBuffer?.length || 0} bytes`,
        `- MIME type: ${mimeType}`,
        `- Model used: ${usingModel}`
      ].join('\n');
    
      // Save the base64 data to a debug file for inspection
      try {
        const fs = await import('fs');
        const path = await import('path');
    
        // Create a debug directory if it doesn't exist
        const debugDir = path.join(process.cwd(), 'debug');
        if (!fs.existsSync(debugDir)) {
          fs.mkdirSync(debugDir, { recursive: true });
        }
    
        // Save the base64 data to a file for debugging
        const debugFilePath = path.join(debugDir, `debug_${Date.now()}_${finalFileName}`);
    
        // Save buffer directly to debug file
        if (imageBuffer) {
          fs.writeFileSync(debugFilePath, imageBuffer);
          console.log(`Saved image data to debug file: ${debugFilePath}`);
          console.log(`Debug file size: ${imageBuffer.length} bytes`);
        } else {
          console.error('No image data to save for debugging');
        }
      } catch (debugError) {
        console.error('Error saving debug file:', debugError);
      }
    
      // Validate input data
      if (!imageBuffer) {
        return {
          content: [{
            type: "text",
            text: "Error: No image data available for upload"
          }],
          isError: true
        };
      }
    
      if (!finalFileName) {
        return {
          content: [{
            type: "text",
            text: "Error: No filename available for upload"
          }],
          isError: true
        };
      }
    
      // STEP 1: Import required modules
      let axios;
      let FormData;
      try {
        axios = (await import('axios')).default;
        FormData = (await import('form-data')).default;
      } catch (importError: any) {
        return {
          content: [{
            type: "text",
            text: `Error importing required modules: ${importError.message || String(importError)}`
          }],
          isError: true
        };
      }
    
      // STEP 2: Prepare image for upload (either via ImgBB or direct base64)
      let imageUrl;
      let uploadMethod = "direct";
    
      // Check if we're using the Ultra model
      const isUsingUltraModel = usingModel.includes('flux-1.1-pro-ultra');
    
      // Check if ImgBB API key is set
      const imgbbApiKey = process.env.IMGBB_API_KEY;
    
      if (imgbbApiKey && imgbbApiKey !== 'your-imgbb-api-key') {
        // If ImgBB API key is set, use ImgBB to get a URL
        try {
          // Create form data for ImgBB
          const formData = new FormData();
          // Convert buffer to base64 for ImgBB upload
          const base64Data = imageBuffer.toString('base64');
          formData.append('image', base64Data);
    
          // Upload to ImgBB with the key as a query parameter
          const imgbbResponse = await axios.post(
            `https://api.imgbb.com/1/upload?key=${imgbbApiKey}`,
            formData
          );
    
          // Get the image URL from ImgBB response
          imageUrl = imgbbResponse.data.data.url;
          uploadMethod = "imgbb";
    
          // Log success
          console.log(`Successfully uploaded image to ImgBB. URL: ${imageUrl}`);
        } catch (imgbbError: any) {
          // Only fall back to direct upload if not using Ultra model
          if (isUsingUltraModel) {
            return {
              content: [{
                type: "text",
                text: `Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}\n\n` +
                      `When using the Ultra model, ImgBB upload is required and cannot be bypassed.\n\n` +
                      `Response data: ${JSON.stringify(imgbbError.response?.data || {}, null, 2)}`
              }],
              isError: true
            };
          }
    
          console.log(`Error uploading to ImgBB: ${imgbbError.message || String(imgbbError)}. Falling back to direct base64 upload.`);
          // Fall back to direct base64 upload for non-Ultra models
          uploadMethod = "direct";
        }
      } else if (!isUsingUltraModel) {
        console.log("No ImgBB API key found. Using direct base64 upload.");
      }
    
      // STEP 4: Import Printify SDK
      let Printify;
      try {
        Printify = (await import('printify-sdk-js')).default;
      } catch (importError: any) {
        return {
          content: [{
            type: "text",
            text: `Error importing Printify SDK: ${importError.message || String(importError)}\n\n` +
                  `ImgBB URL: ${imageUrl}`
          }],
          isError: true
        };
      }
    
      // STEP 5: Create Printify client
      let printifySDK;
      try {
        printifySDK = new Printify({
          accessToken: process.env.PRINTIFY_API_KEY || '',
          shopId: printifyClient ? printifyClient.getCurrentShopId() || undefined : undefined
        });
    
        // Log client creation
        console.log(`Created Printify client with shop ID: ${printifyClient?.getCurrentShopId() || 'undefined'}`);
      } catch (clientError: any) {
        return {
          content: [{
            type: "text",
            text: `Error creating Printify client: ${clientError.message || String(clientError)}\n\n` +
                  `ImgBB URL: ${imageUrl}`
          }],
          isError: true
        };
      }
    
      // STEP 6: Upload the image to Printify
      let image;
      try {
        if (uploadMethod === "imgbb" && imageUrl) {
          // Upload using the URL from ImgBB
          image = await printifySDK.uploads.uploadImage({
            file_name: finalFileName,
            url: imageUrl
          });
          console.log(`Successfully uploaded image to Printify using ImgBB URL. Image ID: ${image.id}`);
        } else {
          // Direct base64 upload
          // Convert buffer to base64 for Printify direct upload
          const base64Data = imageBuffer.toString('base64');
          image = await printifySDK.uploads.uploadImage({
            file_name: finalFileName,
            contents: base64Data
          });
          console.log(`Successfully uploaded image to Printify using direct base64. Image ID: ${image.id}`);
        }
      } catch (uploadError: any) {
        return {
          content: [{
            type: "text",
            text: `Error uploading to Printify: ${uploadError.message || String(uploadError)}\n\n` +
                  `Upload method: ${uploadMethod}${imageUrl ? `\nImgBB URL: ${imageUrl}` : ''}\n\n` +
                  `Response data: ${JSON.stringify(uploadError.response?.data || {}, null, 2)}`
          }],
          isError: true
        };
      }
    
      // STEP 7: Return success response
      const response = formatSuccessResponse(
        'Image Generated and Uploaded Successfully',
        {
          Prompt: prompt,
          Model: usingModel.split('/')[1],
          'Image ID': image.id,
          'File Name': image.file_name,
          Dimensions: `${image.width}x${image.height}`,
          'Preview URL': image.preview_url,
          'Upload Method': uploadMethod === "imgbb" ? "ImgBB URL" : "Direct base64",
          ...(imageUrl ? { 'ImgBB URL': imageUrl } : {}),
          'Upload Details': uploadDetails
        },
        `You can now use this image ID (${image.id}) when creating a product.\n\n` +
        `**Example:**\n` +
        `\`\`\`json\n` +
        `"print_areas": {\n` +
        `  "front": { "position": "front", "imageId": "${image.id}" }\n` +
        `}\n` +
        `\`\`\``
      ) as { content: any[], isError?: boolean };
    
      return response;
    
    
    }
  • Zod input schema defining all parameters for image generation and upload, including prompt, fileName, model overrides, dimensions, format, and model-specific options.
    {
      prompt: z.string().describe("Text prompt for image generation"),
      fileName: z.string().describe("File name for the uploaded image"),
    
      // Optional model override
      model: z.string().optional()
        .describe("Optional: Override the default model. Use get_defaults to see available models"),
    
      // Common parameters for both models
      width: z.number().optional().default(1024).describe("Image width in pixels"),
      height: z.number().optional().default(1024).describe("Image height in pixels"),
      aspectRatio: z.string().optional().describe("Aspect ratio (e.g., '16:9', '4:3', '1:1'). If provided, overrides width and height"),
      outputFormat: z.enum(["jpeg", "png", "webp"]).optional().default("png").describe("Output format"),
      safetyTolerance: z.number().optional().default(2).describe("Safety tolerance (0-6)"),
      seed: z.number().optional().describe("Random seed for reproducible generation"),
      numInferenceSteps: z.number().optional().default(25).describe("Number of inference steps"),
      guidanceScale: z.number().optional().default(7.5).describe("Guidance scale"),
      negativePrompt: z.string().optional().default("low quality, bad quality, sketches").describe("Negative prompt"),
    
      // Flux 1.1 Pro specific parameters
      promptUpsampling: z.boolean().optional()
        .describe("Enable prompt upsampling (Flux 1.1 Pro only)"),
      outputQuality: z.number().optional()
        .describe("Output quality 1-100 (Flux 1.1 Pro only)"),
    
      // Flux 1.1 Pro Ultra specific parameters
      raw: z.boolean().optional()
        .describe("Generate less processed, more natural-looking images (Flux 1.1 Pro Ultra only)"),
      imagePromptStrength: z.number().optional()
        .describe("Image prompt strength 0-1 (Flux 1.1 Pro Ultra only)")
    },
  • Helper function that generates image buffer using ReplicateClient and Sharp image processing, called by the main handler.
    export async function generateImage(
      replicateClient: ReplicateClient,
      prompt: string,
      fileName: string,
      options: any = {}
    ) {
      // No need to track files anymore since we're keeping everything in memory
    
      try {
        // Prepare options with proper naming for the API
        const modelOptions: any = {};
    
        // Set aspect ratio or dimensions
        if (options.aspectRatio) {
          modelOptions.aspectRatio = options.aspectRatio;
        } else {
          // If no aspect ratio is provided, use width and height
          // These will be overridden by the defaults in the DefaultsManager if not provided
          modelOptions.width = options.width || 1024;
          modelOptions.height = options.height || 1024;
        }
    
        // Add common parameters
        if (options.numInferenceSteps) modelOptions.numInferenceSteps = options.numInferenceSteps;
        if (options.guidanceScale) modelOptions.guidanceScale = options.guidanceScale;
        if (options.negativePrompt) modelOptions.negativePrompt = options.negativePrompt;
        if (options.seed !== undefined) modelOptions.seed = options.seed;
        // Always set outputFormat, defaulting to png unless explicitly specified
        modelOptions.outputFormat = options.outputFormat || "png";
        if (options.safetyTolerance !== undefined) modelOptions.safetyTolerance = options.safetyTolerance;
    
        // Add model-specific parameters if provided
        if (options.promptUpsampling !== undefined) modelOptions.promptUpsampling = options.promptUpsampling;
        if (options.outputQuality !== undefined) modelOptions.outputQuality = options.outputQuality;
        if (options.raw !== undefined) modelOptions.raw = options.raw;
        if (options.imagePromptStrength !== undefined) modelOptions.imagePromptStrength = options.imagePromptStrength;
    
        // Add model override if provided
        if (options.model) modelOptions.model = options.model;
    
        // Get the current default model for informational purposes
        const defaultModel = replicateClient.getDefaultModel();
        const usingModel = options.model || defaultModel;
        console.log(`Using model: ${usingModel} (${options.model ? 'override' : 'default'})`);
        console.log(`Prompt: ${prompt}`);
    
        // STEP 1: Generate the image with Replicate
        console.log('Generating image with Replicate...');
        const imageBuffer = await replicateClient.generateImage(prompt, modelOptions);
        console.log(`Image generated successfully, buffer size: ${imageBuffer.length} bytes`);
    
        // STEP 2: Process the image with Sharp
        console.log('Processing image with Sharp...');
    
        // Get the output format from options (already defaulted to png earlier)
        const outputFormat = modelOptions.outputFormat;
        let mimeType: string;
    
        if (outputFormat === 'jpeg' || outputFormat === 'jpg') {
          mimeType = 'image/jpeg';
        } else if (outputFormat === 'webp') {
          mimeType = 'image/webp';
        } else {
          // Default to PNG
          mimeType = 'image/png';
        }
    
        // Process with Sharp and get buffer directly
        let sharpInstance = sharp(imageBuffer);
    
        // Apply format-specific options
        if (outputFormat === 'png') {
          sharpInstance = sharpInstance.png({ quality: 100 });
        } else if (outputFormat === 'jpeg' || outputFormat === 'jpg') {
          sharpInstance = sharpInstance.jpeg({ quality: 100 });
        } else if (outputFormat === 'webp') {
          sharpInstance = sharpInstance.webp({ quality: 100 });
        }
    
        // Get the processed image as a buffer
        const processedBuffer = await sharpInstance.toBuffer();
        console.log(`Image processed successfully, buffer size: ${processedBuffer.length} bytes`);
    
        // Determine the final filename with extension
        const fileExtension = outputFormat === 'jpeg' ? 'jpg' : outputFormat;
        const finalFileName = fileName.endsWith(`.${fileExtension}`) ? fileName : `${fileName}.${fileExtension}`;
    
        // No need to clean up files since we're keeping everything in memory
    
        // Get dimensions from the Sharp metadata
        const metadata = await sharpInstance.metadata();
        const dimensions = `${metadata.width}x${metadata.height}`;
    
        return {
          success: true,
          buffer: processedBuffer,
          mimeType,
          fileName: finalFileName,
          model: usingModel,
          dimensions
        };
      } catch (error: any) {
        console.error('Error generating or processing image:', error);
    
        // No need to clean up files since we're keeping everything in memory
    
        // Get the current default model for informational purposes
        const defaultModel = replicateClient.getDefaultModel();
        const usingModel = options.model || defaultModel;
    
        // Determine which step failed
        const errorStep = error.message.includes('Sharp') ? 'Image Processing' : 'Image Generation';
    
        return {
          success: false,
          error,
          errorResponse: formatErrorResponse(
            error,
            errorStep,
            {
              Prompt: prompt,
              Model: usingModel.split('/')[1],
              Step: errorStep
            },
            [
              'Check that your REPLICATE_API_TOKEN is valid',
              'Try a different model using set-model',
              'Try a more descriptive prompt',
              'Try a different aspect ratio',
              ...(errorStep === 'Image Processing' ? [
                'Make sure Sharp is properly installed'
              ] : [])
            ]
          )
        };
      }
    }

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/TSavo/printify-mcp'

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