Skip to main content
Glama

upload_image

Upload images to the Printify MCP Server by providing a file name and source (URL, local path, or base64 data) for integration with AI-driven print-on-demand workflows.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
fileNameYesFile name
urlYesURL of the image to upload, path to local file, or base64 encoded image data

Implementation Reference

  • src/index.ts:558-599 (registration)
    MCP server.tool registration for 'upload_image', including input schema (fileName, url) and handler function that checks client, determines source type, and delegates to uploadImageToPrintify service
    // Upload image tool
    server.tool(
      "upload_image",
      {
        fileName: z.string().describe("File name"),
        url: z.string().describe("URL of the image to upload, path to local file, or base64 encoded image data")
      },
      async ({ fileName, url }): Promise<{ content: any[], isError?: boolean }> => {
        // Import the printify uploader service
        const { uploadImageToPrintify, determineImageSourceType } = await import('./services/printify-uploader.js');
    
        // Check if client is initialized
        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
          };
        }
    
        // Log the attempt with limited information for privacy
        const sourceType = determineImageSourceType(url);
        const sourcePreview = sourceType === 'url' ? url.substring(0, 30) + '...' :
                             sourceType === 'file' ? url : // Show full file path
                             url.substring(0, 30) + '...';
    
        console.log(`Attempting to upload image: ${fileName} from ${sourceType} source: ${sourcePreview}`);
    
        // Call the service
        const result = await uploadImageToPrintify(printifyClient, fileName, url);
    
        // Return the result
        if (result.success) {
          return result.response as { content: any[], isError?: boolean };
        } else {
          return result.errorResponse as { content: any[], isError: boolean };
        }
      }
    );
  • Main handler function uploadImageToPrintify that validates shop, handles file/url/base64 sources, verifies files, and calls PrintifyAPI.uploadImage
    export async function uploadImageToPrintify(
      printifyClient: PrintifyAPI,
      fileName: string,
      source: string
    ) {
      try {
        // Validate shop is selected
        const currentShop = printifyClient.getCurrentShop();
        if (!currentShop) {
          throw new Error('No shop is currently selected. Use the list-shops and switch-shop tools to select a shop.');
        }
    
        // Determine the source type
        const sourceType = determineImageSourceType(source);
        console.log(`Uploading image to Printify from ${sourceType} source`);
    
        let image;
    
        if (sourceType === 'file') {
          // Handle file upload
          const filePath = normalizeFilePath(source);
    
          // Validate file exists
          const fileInfo = getFileInfo(filePath);
          if (!fileInfo.exists) {
            throw new Error(`File not found: ${filePath}`);
          }
    
          console.log(`Uploading file to Printify: ${filePath}`);
          console.log(`File size: ${fileInfo.size} bytes`);
    
          // Check file size limits
          if (fileInfo.size && fileInfo.size > 20 * 1024 * 1024) { // 20MB limit
            throw new Error(`File is too large (${Math.round(fileInfo.size / (1024 * 1024))}MB). Maximum size is 20MB.`);
          }
    
          // Verify the file exists and is readable before uploading
          try {
            // Use dynamic imports for fs and path
            const fs = await import('fs');
            const path = await import('path');
    
            if (fs.existsSync(filePath)) {
              const stats = fs.statSync(filePath);
              console.log(`File verification before upload:`);
              console.log(`- Path: ${filePath}`);
              console.log(`- Absolute path: ${path.resolve(filePath)}`);
              console.log(`- Size: ${stats.size} bytes`);
              console.log(`- Created: ${stats.birthtime}`);
              console.log(`- Permissions: ${stats.mode.toString(8)}`);
    
              try {
                fs.accessSync(filePath, fs.constants.R_OK);
                console.log(`- Readable: Yes`);
              } catch (e: any) {
                console.log(`- Readable: No - ${e.message || e}`);
              }
    
              // Try to read the first few bytes to verify the file is readable
              try {
                const fd = fs.openSync(filePath, 'r');
                const buffer = Buffer.alloc(10);
                const bytesRead = fs.readSync(fd, buffer, 0, 10, 0);
                fs.closeSync(fd);
                console.log(`- Read test: Successfully read ${bytesRead} bytes`);
              } catch (readError: any) {
                console.error(`- Read test failed: ${readError.message || readError}`);
              }
    
              // Create a debug directory and copy the file there
              try {
                const debugDir = path.join(process.cwd(), 'debug');
                if (!fs.existsSync(debugDir)) {
                  fs.mkdirSync(debugDir, { recursive: true });
                }
    
                const debugFilePath = path.join(debugDir, `upload_${Date.now()}_${path.basename(filePath)}`);
                fs.copyFileSync(filePath, debugFilePath);
                console.log(`- Debug copy: ${debugFilePath}`);
              } catch (copyError: any) {
                console.error(`- Debug copy failed: ${copyError.message || copyError}`);
              }
            } else {
              console.error(`ERROR: File does not exist at upload time: ${filePath}`);
              throw new Error(`File does not exist at upload time: ${filePath}`);
            }
          } catch (verifyError: any) {
            console.error('Error verifying file before upload:', verifyError);
            throw new Error(`Failed to verify file before upload: ${verifyError.message || verifyError}`);
          }
    
          // Upload to Printify
          console.log(`Attempting to upload file to Printify: ${filePath}`);
          image = await printifyClient.uploadImage(fileName, filePath);
          console.log(`Upload successful! Image ID: ${image.id}`);
          console.log(`Preview URL: ${image.preview_url}`);
        } else {
          // For URLs and base64 strings, upload directly
          image = await printifyClient.uploadImage(fileName, source);
        }
    
        console.log(`Image uploaded successfully! ID: ${image.id}`);
    
        return {
          success: true,
          image,
          response: formatSuccessResponse(
            'Image Uploaded Successfully',
            {
              'Image ID': image.id,
              'File Name': image.file_name,
              'Dimensions': `${image.width}x${image.height}`,
              'Preview URL': image.preview_url
            },
            `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` +
            `\`\`\``
          )
        };
      } catch (error: any) {
        console.error('Error uploading image to Printify:', error);
    
        // Determine source type for better error messages
        const sourceType = determineImageSourceType(source);
        const sourceTypeLabel = sourceType === 'url' ? 'URL' :
                               sourceType === 'file' ? 'file path' :
                               'base64 string';
    
        // Create appropriate troubleshooting tips based on source type
        const tips = [
          'Check that your Printify API key is valid',
          'Ensure your Printify account is properly connected'
        ];
    
        if (sourceType === 'url') {
          tips.push('Make sure the URL is publicly accessible and points directly to an image file');
          tips.push('The URL must start with http:// or https://');
        } else if (sourceType === 'file') {
          tips.push('Make sure the file exists and is readable');
          tips.push('Check that the path is correct and includes the full path to the file');
          tips.push('The file must be a valid image format (PNG, JPEG, SVG)');
          tips.push('Recommended resolution for JPEG/PNG files is 300 DPI');
          tips.push('Maximum file size is 20MB');
        } else {
          tips.push('Make sure the base64 string is valid and represents an image');
        }
    
        // Gather as much diagnostic information as possible
        const diagnosticInfo: any = {
          FileName: fileName,
          SourceType: sourceTypeLabel,
          Source: sourceType === 'url' ? source : (sourceType === 'file' ? source : `${source.substring(0, 30)}...`),
          CurrentShop: printifyClient.getCurrentShop(),
          ErrorType: error.constructor.name,
          ErrorStack: error.stack,
          ErrorMessage: error.message,
          CurrentWorkingDirectory: process.cwd(),
          NodeVersion: process.version,
          Platform: process.platform,
          // Add Printify client information
          PrintifyClientInitialized: !!printifyClient,
          PrintifyShopId: printifyClient.getCurrentShopId(),
          PrintifyAvailableShops: printifyClient.getAvailableShops().length
        };
    
        // Add file-specific diagnostics if it's a file
        if (sourceType === 'file') {
          const filePath = normalizeFilePath(source);
          const fileInfo = getFileInfo(filePath);
          diagnosticInfo.FileExists = fileInfo.exists;
          diagnosticInfo.FileSize = fileInfo.exists ? fileInfo.size + ' bytes' : 'N/A';
    
          // Try to get more file details if it exists
          if (fileInfo.exists) {
            try {
              // Use dynamic imports for fs
              const fsPromise = import('fs');
              const pathPromise = import('path');
    
              // Wait for imports to complete
              const [fsModule, pathModule] = await Promise.all([fsPromise, pathPromise]);
              const fs = fsModule.default || fsModule;
              const path = pathModule.default || pathModule;
    
              const stats = fs.statSync(filePath);
              diagnosticInfo.FileCreated = stats.birthtime;
              diagnosticInfo.FileModified = stats.mtime;
              diagnosticInfo.FilePermissions = stats.mode.toString(8);
              diagnosticInfo.AbsolutePath = path.resolve(filePath);
    
              // Try to read the first few bytes to verify content
              try {
                const buffer = Buffer.alloc(10);
                const fd = fs.openSync(filePath, 'r');
                // Read the first 10 bytes
                const bytesRead = fs.readSync(fd, buffer, 0, 10, 0);
                fs.closeSync(fd);
                diagnosticInfo.FileReadable = true;
                diagnosticInfo.BytesRead = bytesRead;
                diagnosticInfo.FileFirstBytes = buffer.toString('hex').substring(0, 20);
    
                // Check file signature to determine if it's a valid image
                const hexSignature = buffer.toString('hex').substring(0, 8).toLowerCase();
                let fileType = 'unknown';
    
                // Check common image signatures
                if (hexSignature.startsWith('89504e47')) {
                  fileType = 'PNG';
                } else if (hexSignature.startsWith('ffd8ffe')) {
                  fileType = 'JPEG';
                } else if (hexSignature.startsWith('52494646')) {
                  fileType = 'WEBP';
                } else if (hexSignature.startsWith('3c737667')) {
                  fileType = 'SVG';
                }
    
                diagnosticInfo.DetectedFileType = fileType;
                diagnosticInfo.FileSignature = hexSignature;
              } catch (readError: any) {
                diagnosticInfo.FileReadable = false;
                diagnosticInfo.FileReadError = readError.message || String(readError);
              }
            } catch (statError: any) {
              diagnosticInfo.FileStatError = statError.message || String(statError);
            }
          }
        }
    
        // Add error details if available
        if (error.response) {
          diagnosticInfo.PrintifyResponseStatus = error.response.status;
          diagnosticInfo.PrintifyResponseStatusText = error.response.statusText;
          diagnosticInfo.PrintifyResponseData = error.response.data;
          diagnosticInfo.PrintifyResponseHeaders = error.response.headers;
        }
    
        return {
          success: false,
          error,
          errorResponse: formatErrorResponse(
            error,
            `Printify Upload (${sourceTypeLabel})`,
            diagnosticInfo,
            tips
          )
        };
      }
    }
  • Zod input schema for upload_image tool: fileName (string), url (string supporting URL, local file path, or base64)
    {
      fileName: z.string().describe("File name"),
      url: z.string().describe("URL of the image to upload, path to local file, or base64 encoded image data")
    },
  • PrintifyAPI.uploadImage low-level helper: handles URL direct upload, file reading+Sharp+base64 conversion, data URL or plain base64 upload to Printify SDK
    async uploadImage(fileName: string, source: string) {
      try {
        console.log(`Uploading image ${fileName}`);
    
        // If the source starts with http:// or https://, use the URL upload method
        if (source.startsWith('http://') || source.startsWith('https://')) {
          console.log(`Uploading from URL: ${source.substring(0, 30)}...`);
          return await this.client.uploads.uploadImage({ file_name: fileName, url: source });
        }
    
        // If it's a file path, try to read the file and convert to base64
        if (source.startsWith('file://') || source.includes(':\\') || source.includes(':/') || !source.startsWith('data:')) {
          try {
            console.log(`Attempting to read file from: ${source}`);
    
            // Handle file:// protocol
            let filePath = source;
            if (source.startsWith('file:///')) {
              filePath = source.replace('file:///', '');
            }
    
            // Handle Windows paths
            if (filePath.startsWith('/')) {
              filePath = filePath.substring(1);
            }
    
            console.log(`Normalized file path: ${filePath}`);
    
            // Check if file exists
            if (!fs.existsSync(filePath)) {
              const error = new Error(`File not found: ${filePath}`);
              console.error('File not found error:', error);
              console.error('Current working directory:', process.cwd());
              console.error('File path type:', typeof filePath);
              console.error('Absolute path check:', path.isAbsolute(filePath) ? 'Absolute' : 'Relative');
    
              // Try to list the directory contents if possible
              try {
                const dir = path.dirname(filePath);
                if (fs.existsSync(dir)) {
                  console.error('Directory exists. Contents:', fs.readdirSync(dir));
                } else {
                  console.error('Parent directory does not exist:', dir);
                }
              } catch (dirError) {
                console.error('Error checking directory:', dirError);
              }
    
              throw error;
            }
    
            // Get file stats
            const stats = fs.statSync(filePath);
            console.log(`File size: ${stats.size} bytes`);
    
            if (stats.size === 0) {
              throw new Error(`File is empty: ${filePath}`);
            }
    
            if (stats.size > 10 * 1024 * 1024) { // 10MB limit
              throw new Error(`File is too large (${Math.round(stats.size / (1024 * 1024))}MB). Maximum size is 10MB.`);
            }
    
            // Process the image with Sharp
            console.log('Processing image with Sharp before uploading...');
    
            // Use Sharp directly
            const sharpInstance = sharp(filePath);
            const outputFormat = path.extname(filePath).toLowerCase() === '.jpg' ||
                                path.extname(filePath).toLowerCase() === '.jpeg' ? 'jpeg' : 'png';
    
            // Convert to the appropriate format
            if (outputFormat === 'jpeg') {
              sharpInstance.jpeg({ quality: 100 });
            } else {
              sharpInstance.png({ quality: 100 });
            }
    
            // Get the buffer
            const buffer = await sharpInstance.toBuffer();
            console.log(`Image processed successfully: ${buffer.length} bytes`);
    
            // Determine the MIME type
            const mimeType = outputFormat === 'jpeg' ? 'image/jpeg' : 'image/png';
    
            // Convert to base64
            const base64Data = buffer.toString('base64');
            console.log(`Converted to base64 string of length ${base64Data.length}`);
    
            // Create data URL with the proper MIME type prefix
            const dataUrl = `data:${mimeType};base64,${base64Data}`;
            console.log(`Uploading with data URL (MIME type: ${mimeType})`);
    
            try {
              console.log(`Uploading to Printify with file_name: ${fileName}, contents length: ${base64Data.length}`);
              // Use the dataUrl instead of just the base64Data
              const result = await this.client.uploads.uploadImage({ file_name: fileName, contents: dataUrl.split(',')[1] });
              console.log('Upload successful, result:', result);
              return result;
            } catch (uploadError: any) {
              console.error('Error during Printify upload:', uploadError);
              if (uploadError.response) {
                console.error('Response status:', uploadError.response.status);
                console.error('Response data:', JSON.stringify(uploadError.response.data, null, 2));
              }
              throw uploadError;
            }
          } catch (error: any) {
            console.error('Error reading file:', error);
            const errorMessage = error.message || 'Unknown error';
    
            // Create a detailed error message with troubleshooting information
            let detailedError = `Failed to process file ${source}: ${errorMessage}\n\n`;
            detailedError += 'Troubleshooting steps:\n';
            detailedError += '1. Check if the file exists and is readable\n';
            detailedError += '2. Make sure the file is a valid image (PNG, JPEG, etc.)\n';
            detailedError += '3. Try using a URL or base64 encoded string instead\n';
            detailedError += '\nFile processing details:\n';
            detailedError += `- Attempted to read from: ${source}\n`;
            detailedError += `- Current working directory: ${process.cwd()}\n`;
    
            // Add stack trace
            if (error.stack) {
              detailedError += `\nStack trace:\n${error.stack}\n`;
            }
    
            throw new Error(detailedError);
          }
        } else if (source.startsWith('data:image/')) {
          // If source is base64 data with data URL prefix
          // Extract the base64 content
          const base64Content = source.split(',')[1];
          console.log(`Uploading image with base64 data from data URL (length: ${base64Content.length})`);
          return await this.client.uploads.uploadImage({ file_name: fileName, contents: base64Content });
        } else {
          // Otherwise, assume it's a base64 encoded string without prefix
          console.log(`Uploading image with base64 data (length: ${source.length})`);
          return await this.client.uploads.uploadImage({ file_name: fileName, contents: source });
        }
      } catch (error: any) {
        console.error('Error uploading image:', error);
    
        // Add detailed debugging information
        const debugInfo: any = {
          fileName,
          sourceType: typeof source,
          sourceLength: source.length,
          currentWorkingDir: process.cwd(),
          errorMessage: error.message,
          errorStack: error.stack
        };
    
        console.error('Detailed upload error information:', JSON.stringify(debugInfo, null, 2));
    
        // If there's a response object, extract and log the full response data
        if (error.response) {
          console.error('Response status:', error.response.status);
          console.error('Response data:', JSON.stringify(error.response.data, null, 2));
    
          // Add the full response data to the debug info
          debugInfo.responseStatus = error.response.status;
          debugInfo.responseData = error.response.data;
        }
    
        throw this.enhanceError(error, debugInfo);
      }
    }
  • determineImageSourceType utility to classify image source as 'url', 'file', or 'base64' for routing logic
    export function determineImageSourceType(source: string): 'url' | 'file' | 'base64' {
      // Check if it's a URL
      if (source.startsWith('http://') || source.startsWith('https://')) {
        return 'url';
      }
    
      // Check if it's a file path
      if (source.includes(':\\') || source.includes(':/') || source.startsWith('/') || source.includes('\\')) {
        return 'file';
      }
    
      // Otherwise assume it's base64
      return 'base64';
    }

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