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'
              ] : [])
            ]
          )
        };
      }
    }
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Tool has no description.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness1/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Tool has no description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Tool has no description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Tool has no description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose1/5

Does the description clearly state what the tool does and how it differs from similar tools?

Tool has no description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tool has no description.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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

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