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
| Name | Required | Description | Default |
|---|---|---|---|
| fileName | Yes | File name | |
| url | Yes | URL 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 }; } } );
- src/services/printify-uploader.ts:49-300 (handler)Main handler function uploadImageToPrintify that validates shop, handles file/url/base64 sources, verifies files, and calls PrintifyAPI.uploadImageexport 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 ) }; } }
- src/index.ts:561-564 (schema)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") },
- src/printify-api.ts:478-644 (helper)PrintifyAPI.uploadImage low-level helper: handles URL direct upload, file reading+Sharp+base64 conversion, data URL or plain base64 upload to Printify SDKasync 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 logicexport 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'; }