UNPKG

levisnkyyyy-images-mcp

Version:

Model Context Protocol server for AI image and video generation using LiteLLM and fal.ai

1,101 lines (1,100 loc) 67.1 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const client_1 = require("@fal-ai/client"); const LITELLM_URL = process.env.LITELLM_URL || "http://litellm:4000"; const LITELLM_KEY = process.env.LITELLM_KEY || ""; const FAL_API_KEY = process.env.FAL_API_KEY || ""; const FS_PREFIX = process.env.FS_PREFIX || ""; // Helper function to remove file extension from image/video name function removeFileExtension(filename) { // Remove common image and video file extensions return filename.replace(/\.(png|jpg|jpeg|gif|bmp|webp|svg|tiff|tif|ico|mp4|avi|mov|wmv|flv|mkv|webm|m4v|mpg|mpeg)$/i, ''); } // Configure fal client with API key if (FAL_API_KEY) { client_1.fal.config({ credentials: FAL_API_KEY, }); } const server = new index_js_1.Server({ name: "images-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, }); server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { return { tools: [ { name: "openai_image_generation", description: "Generate an image using OpenAI's DALL-E model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate an image from", }, project_folder: { type: "string", description: "Path to the folder where generated images will be saved", }, image_name: { type: "string", description: "Base filename for the generated image(s) (without extension)", }, size: { type: "string", description: "The size of the generated image", enum: ["256x256", "512x512", "1024x1024"], default: "1024x1024", }, n: { type: "number", description: "Number of images to generate", minimum: 1, maximum: 4, default: 1, }, }, required: ["prompt", "project_folder", "image_name"], }, }, { name: "openai_image_edit", description: "Edit an existing image based on a prompt using OpenAI's DALL-E model", inputSchema: { type: "object", properties: { image_path: { type: "string", description: "Full path to the image file to edit", }, mask: { type: "string", description: "Base64 encoded mask where edits should be applied", }, prompt: { type: "string", description: "The text prompt describing how to edit the image", }, project_folder: { type: "string", description: "Path to the folder where edited images will be saved", }, image_name: { type: "string", description: "Base filename for the edited image(s) (without extension)", }, size: { type: "string", description: "The size of the edited image", enum: ["256x256", "512x512", "1024x1024"], default: "1024x1024", }, n: { type: "number", description: "Number of edited images to generate", minimum: 1, maximum: 4, default: 1, }, }, required: ["image_path", "prompt", "project_folder", "image_name"], }, }, { name: "imagen_image_generation", description: "Generate an image using Google's Imagen 3.0 model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate an image from", }, project_folder: { type: "string", description: "Path to the folder where generated images will be saved", }, image_name: { type: "string", description: "Base filename for the generated image(s) (without extension)", }, size: { type: "string", description: "The size of the generated image", enum: ["1024x1024", "1536x1536"], default: "1024x1024", }, n: { type: "number", description: "Number of images to generate", minimum: 1, maximum: 4, default: 1, }, }, required: ["prompt", "project_folder", "image_name"], }, }, { name: "imagen4_image_generation", description: "Generate an image using Google's Imagen 4.0 model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate an image from", }, project_folder: { type: "string", description: "Path to the folder where generated images will be saved", }, image_name: { type: "string", description: "Base filename for the generated image(s) (without extension)", }, size: { type: "string", description: "The size of the generated image", enum: ["1024x1024", "1536x1536", "2048x2048"], default: "1024x1024", }, n: { type: "number", description: "Number of images to generate", minimum: 1, maximum: 4, default: 1, }, }, required: ["prompt", "project_folder", "image_name"], }, }, { name: "imagen4_ultra_image_generation", description: "Generate an image using Google's Imagen 4.0 Ultra model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate an image from", }, project_folder: { type: "string", description: "Path to the folder where generated images will be saved", }, image_name: { type: "string", description: "Base filename for the generated image(s) (without extension)", }, size: { type: "string", description: "The size of the generated image", enum: ["1024x1024", "1536x1536", "2048x2048", "4096x4096"], default: "2048x2048", }, n: { type: "number", description: "Number of images to generate", minimum: 1, maximum: 4, default: 1, }, }, required: ["prompt", "project_folder", "image_name"], }, }, { name: "flux_pro_image_generation", description: "Generate an image using fal.ai's Flux Pro model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate an image from", }, project_folder: { type: "string", description: "Path to the folder where generated images will be saved", }, image_name: { type: "string", description: "Base filename for the generated image(s) (without extension)", }, width: { type: "number", description: "Width of the generated image", minimum: 256, maximum: 2048, default: 1024, }, height: { type: "number", description: "Height of the generated image", minimum: 256, maximum: 2048, default: 1024, }, num_inference_steps: { type: "number", description: "Number of inference steps", minimum: 1, maximum: 100, default: 25, }, guidance_scale: { type: "number", description: "Guidance scale for generation", minimum: 1, maximum: 20, default: 7.5, }, }, required: ["prompt", "project_folder", "image_name"], }, }, { name: "flux_max_image_generation", description: "Generate an image using fal.ai's Flux Max model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate an image from", }, project_folder: { type: "string", description: "Path to the folder where generated images will be saved", }, image_name: { type: "string", description: "Base filename for the generated image(s) (without extension)", }, guidance_scale: { type: "number", description: "Guidance scale for generation", minimum: 1, maximum: 20, default: 3.5, }, aspect_ratio: { type: "string", description: "Aspect ratio of the generated image", enum: ["1:1", "16:9", "9:16", "4:3", "3:4"], default: "1:1", }, safety_tolerance: { type: "string", description: "Safety tolerance level", enum: ["1", "2", "3", "4", "5"], default: "2", }, }, required: ["prompt", "project_folder", "image_name"], }, }, { name: "flux_pro_image_edit", description: "Edit an existing image using fal.ai's Flux Pro image-to-image model", inputSchema: { type: "object", properties: { image_path: { type: "string", description: "Full path to the image file to edit", }, prompt: { type: "string", description: "The text prompt describing how to edit the image", }, project_folder: { type: "string", description: "Path to the folder where edited images will be saved", }, image_name: { type: "string", description: "Base filename for the edited image(s) (without extension)", }, guidance_scale: { type: "number", description: "Guidance scale for generation", minimum: 1, maximum: 20, default: 3.5, }, safety_tolerance: { type: "string", description: "Safety tolerance level", enum: ["1", "2", "3", "4", "5"], default: "2", }, }, required: ["image_path", "prompt", "project_folder", "image_name"], }, }, { name: "veo3_video_generation", description: "Generate a video using fal.ai's Veo3 model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate a video from", }, project_folder: { type: "string", description: "Path to the folder where generated video will be saved", }, video_name: { type: "string", description: "Base filename for the generated video (without extension)", }, duration: { type: "number", description: "Duration of the video in seconds", minimum: 1, maximum: 10, default: 8, }, aspect_ratio: { type: "string", description: "Aspect ratio of the video", enum: ["16:9", "9:16", "1:1"], default: "16:9", }, enhance_prompt: { type: "boolean", description: "Whether to enhance the prompt", default: true, }, generate_audio: { type: "boolean", description: "Whether to generate audio for the video", default: true, }, }, required: ["prompt", "project_folder", "video_name"], }, }, { name: "kling_video_image_to_video", description: "Generate a video from an image using Kling Video v2 model", inputSchema: { type: "object", properties: { image_path: { type: "string", description: "Full path to the image file to convert to video", }, prompt: { type: "string", description: "The text prompt describing the video generation from the image", }, project_folder: { type: "string", description: "Path to the folder where generated video will be saved", }, video_name: { type: "string", description: "Base filename for the generated video (without extension)", }, duration: { type: "string", description: "Duration of the video in seconds", enum: ["5", "10"], default: "5", }, aspect_ratio: { type: "string", description: "Aspect ratio of the video", enum: ["16:9", "9:16", "1:1"], default: "16:9", }, negative_prompt: { type: "string", description: "What to avoid in the video generation", }, cfg_scale: { type: "number", description: "Classifier-free guidance scale", minimum: 0, maximum: 1, default: 0.5, }, }, required: ["image_path", "prompt", "project_folder", "video_name"], }, }, { name: "seedance_video_generation", description: "Generate a video using ByteDance's SeedDance v1 Lite text-to-video model", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "The text prompt to generate a video from", }, project_folder: { type: "string", description: "Path to the folder where generated video will be saved", }, video_name: { type: "string", description: "Base filename for the generated video (without extension)", }, aspect_ratio: { type: "string", description: "Aspect ratio of the video", enum: ["16:9", "4:3", "1:1", "9:21"], default: "16:9", }, resolution: { type: "string", description: "Resolution of the video", enum: ["480p", "720p"], default: "720p", }, duration: { type: "string", description: "Duration of the video in seconds", enum: ["5", "10"], default: "5", }, }, required: ["prompt", "project_folder", "video_name"], }, }, { name: "seedance_image_to_video", description: "Generate a video from an image using ByteDance's SeedDance v1 Lite image-to-video model", inputSchema: { type: "object", properties: { image_path: { type: "string", description: "Full path to the image file to convert to video", }, prompt: { type: "string", description: "The text prompt describing the video generation from the image", }, project_folder: { type: "string", description: "Path to the folder where generated video will be saved", }, video_name: { type: "string", description: "Base filename for the generated video (without extension)", }, resolution: { type: "string", description: "Resolution of the video", enum: ["480p", "720p"], default: "720p", }, duration: { type: "string", description: "Duration of the video in seconds", enum: ["5", "10"], default: "5", }, }, required: ["image_path", "prompt", "project_folder", "video_name"], }, }, ], }; }); server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const toolName = request.params.name; if (toolName === "openai_image_generation") { const { prompt, project_folder, image_name: rawImageName, size = "1024x1024", n = 1 } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { const response = await fetch(`${LITELLM_URL}/v1/images/generations`, { method: "POST", headers: { "Content-Type": "application/json", ...(LITELLM_KEY && { "Authorization": `Bearer ${LITELLM_KEY}` }), }, body: JSON.stringify({ model: "gpt-image-1-openai", prompt, size, n, response_format: "b64_json", }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} - ${errorText}`); } const data = await response.json(); // Ensure the project folder exists // await fs.mkdir(project_folder, { recursive: true }); // Save each generated image const savedImages = []; for (let i = 0; i < data.data.length; i++) { const imageData = data.data[i]; // Extract base64 data from URL or b64_json let base64Data; console.log(`Received image data: ${Object.keys(imageData)}`); if (imageData.b64_json) { base64Data = imageData.b64_json; } else if (imageData.url && imageData.url.startsWith('data:image')) { // Extract base64 from data URL base64Data = imageData.url.split(',')[1]; } else if (imageData.url) { // If it's a regular URL, fetch the image const imgResponse = await fetch(imageData.url); const buffer = await imgResponse.arrayBuffer(); base64Data = Buffer.from(buffer).toString('base64'); } else { throw new Error('No image data found in response'); } // Determine filename const filename = n > 1 ? `${image_name}_${i + 1}.png` : `${image_name}.png`; const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; const filepath = path_1.default.join(folder, filename); await promises_1.default.access(folder, promises_1.default.constants.W_OK); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(base64Data, 'base64')); savedImages.push(filename); } return { content: [ { type: "text", text: `Successfully generated and saved ${n} image(s) with prompt: "${prompt}"`, }, { type: "text", text: `Saved to: ${savedImages.join(', ')}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating image: ${error instanceof Error ? error.message : String(error)}`, }, { type: "text", text: `Stack trace: ${error instanceof Error ? error.stack : 'No stack trace available'}`, }, ], isError: true, }; } } else if (toolName === "openai_image_edit") { const { image_path, mask, prompt, project_folder, image_name: rawImageName, size = "1024x1024", n = 1 } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { // Read the image file and convert to base64 const fullImagePath = FS_PREFIX ? path_1.default.join(FS_PREFIX, image_path) : image_path; const imageBuffer = await promises_1.default.readFile(fullImagePath); const imageBase64 = imageBuffer.toString('base64'); const response = await fetch(`${LITELLM_URL}/v1/images/edits`, { method: "POST", headers: { "Content-Type": "application/json", ...(LITELLM_KEY && { "Authorization": `Bearer ${LITELLM_KEY}` }), }, body: JSON.stringify({ model: "gpt-image-1-openai", image: imageBase64, mask, prompt, size, n, response_format: "b64_json", }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} - ${errorText}`); } const data = await response.json(); // Ensure the project folder exists const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; await promises_1.default.mkdir(folder, { recursive: true }); // Save each edited image const savedImages = []; for (let i = 0; i < data.data.length; i++) { const imageData = data.data[i]; // Extract base64 data from URL or b64_json let base64Data; if (imageData.b64_json) { base64Data = imageData.b64_json; } else if (imageData.url && imageData.url.startsWith('data:image')) { // Extract base64 from data URL base64Data = imageData.url.split(',')[1]; } else if (imageData.url) { // If it's a regular URL, fetch the image const imgResponse = await fetch(imageData.url); const buffer = await imgResponse.arrayBuffer(); base64Data = Buffer.from(buffer).toString('base64'); } else { throw new Error('No image data found in response'); } // Determine filename const filename = n > 1 ? `${image_name}_${i + 1}.png` : `${image_name}.png`; const filepath = path_1.default.join(folder, filename); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(base64Data, 'base64')); savedImages.push(filename); } return { content: [ { type: "text", text: `Successfully edited and saved ${n} image(s) with prompt: "${prompt}"`, }, { type: "text", text: `Saved to: ${savedImages.join(', ')}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error editing image: ${error instanceof Error ? error.message : String(error)}`, }, { type: "text", text: `Stack trace: ${error instanceof Error ? error.stack : 'No stack trace available'}`, }, ], isError: true, }; } } else if (toolName === "imagen_image_generation") { const { prompt, project_folder, image_name: rawImageName, size = "1024x1024", n = 1 } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { const response = await fetch(`${LITELLM_URL}/v1/images/generations`, { method: "POST", headers: { "Content-Type": "application/json", ...(LITELLM_KEY && { "Authorization": `Bearer ${LITELLM_KEY}` }), }, body: JSON.stringify({ model: "imagen-3.0-generate-002", prompt, size, n, response_format: "b64_json", }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} - ${errorText}`); } const data = await response.json(); // Save each generated image const savedImages = []; for (let i = 0; i < data.data.length; i++) { const imageData = data.data[i]; // Extract base64 data from URL or b64_json let base64Data; console.log(`Received image data: ${Object.keys(imageData)}`); if (imageData.b64_json) { base64Data = imageData.b64_json; } else if (imageData.url && imageData.url.startsWith('data:image')) { // Extract base64 from data URL base64Data = imageData.url.split(',')[1]; } else if (imageData.url) { // If it's a regular URL, fetch the image const imgResponse = await fetch(imageData.url); const buffer = await imgResponse.arrayBuffer(); base64Data = Buffer.from(buffer).toString('base64'); } else { throw new Error('No image data found in response'); } // Determine filename const filename = n > 1 ? `${image_name}_${i + 1}.png` : `${image_name}.png`; const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; const filepath = path_1.default.join(folder, filename); await promises_1.default.access(folder, promises_1.default.constants.W_OK); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(base64Data, 'base64')); savedImages.push(filename); } return { content: [ { type: "text", text: `Successfully generated and saved ${n} image(s) with Imagen 3.0 using prompt: "${prompt}"`, }, { type: "text", text: `Saved to: ${savedImages.join(', ')}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating image with Imagen: ${error instanceof Error ? error.message : String(error)}`, }, { type: "text", text: `Stack trace: ${error instanceof Error ? error.stack : 'No stack trace available'}`, }, ], isError: true, }; } } else if (toolName === "imagen4_image_generation") { const { prompt, project_folder, image_name: rawImageName, size = "1024x1024", n = 1 } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { const response = await fetch(`${LITELLM_URL}/v1/images/generations`, { method: "POST", headers: { "Content-Type": "application/json", ...(LITELLM_KEY && { "Authorization": `Bearer ${LITELLM_KEY}` }), }, body: JSON.stringify({ model: "imagen-4.0-generate", prompt, size, n, response_format: "b64_json", }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} - ${errorText}`); } const data = await response.json(); // Save each generated image const savedImages = []; for (let i = 0; i < data.data.length; i++) { const imageData = data.data[i]; // Extract base64 data from URL or b64_json let base64Data; console.log(`Received image data: ${Object.keys(imageData)}`); if (imageData.b64_json) { base64Data = imageData.b64_json; } else if (imageData.url && imageData.url.startsWith('data:image')) { // Extract base64 from data URL base64Data = imageData.url.split(',')[1]; } else if (imageData.url) { // If it's a regular URL, fetch the image const imgResponse = await fetch(imageData.url); const buffer = await imgResponse.arrayBuffer(); base64Data = Buffer.from(buffer).toString('base64'); } else { throw new Error('No image data found in response'); } // Determine filename const filename = n > 1 ? `${image_name}_${i + 1}.png` : `${image_name}.png`; const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; const filepath = path_1.default.join(folder, filename); await promises_1.default.access(folder, promises_1.default.constants.W_OK); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(base64Data, 'base64')); savedImages.push(filename); } return { content: [ { type: "text", text: `Successfully generated and saved ${n} image(s) with Imagen 4.0 using prompt: "${prompt}"`, }, { type: "text", text: `Saved to: ${savedImages.join(', ')}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating image with Imagen 4.0: ${error instanceof Error ? error.message : String(error)}`, }, { type: "text", text: `Stack trace: ${error instanceof Error ? error.stack : 'No stack trace available'}`, }, ], isError: true, }; } } else if (toolName === "imagen4_ultra_image_generation") { const { prompt, project_folder, image_name: rawImageName, size = "2048x2048", n = 1 } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { const response = await fetch(`${LITELLM_URL}/v1/images/generations`, { method: "POST", headers: { "Content-Type": "application/json", ...(LITELLM_KEY && { "Authorization": `Bearer ${LITELLM_KEY}` }), }, body: JSON.stringify({ model: "imagen-4.0-ultra-generate", prompt, size, n, response_format: "b64_json", }), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`API request failed: ${response.status} - ${errorText}`); } const data = await response.json(); // Save each generated image const savedImages = []; for (let i = 0; i < data.data.length; i++) { const imageData = data.data[i]; // Extract base64 data from URL or b64_json let base64Data; console.log(`Received image data: ${Object.keys(imageData)}`); if (imageData.b64_json) { base64Data = imageData.b64_json; } else if (imageData.url && imageData.url.startsWith('data:image')) { // Extract base64 from data URL base64Data = imageData.url.split(',')[1]; } else if (imageData.url) { // If it's a regular URL, fetch the image const imgResponse = await fetch(imageData.url); const buffer = await imgResponse.arrayBuffer(); base64Data = Buffer.from(buffer).toString('base64'); } else { throw new Error('No image data found in response'); } // Determine filename const filename = n > 1 ? `${image_name}_${i + 1}.png` : `${image_name}.png`; const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; const filepath = path_1.default.join(folder, filename); await promises_1.default.access(folder, promises_1.default.constants.W_OK); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(base64Data, 'base64')); savedImages.push(filename); } return { content: [ { type: "text", text: `Successfully generated and saved ${n} image(s) with Imagen 4.0 Ultra using prompt: "${prompt}"`, }, { type: "text", text: `Saved to: ${savedImages.join(', ')}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating image with Imagen 4.0 Ultra: ${error instanceof Error ? error.message : String(error)}`, }, { type: "text", text: `Stack trace: ${error instanceof Error ? error.stack : 'No stack trace available'}`, }, ], isError: true, }; } } else if (toolName === "flux_pro_image_generation") { const { prompt, project_folder, image_name: rawImageName, width = 1024, height = 1024, num_inference_steps = 25, guidance_scale = 7.5 } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { if (!FAL_API_KEY) { throw new Error("FAL_API_KEY environment variable is not set. Please set it to use fal.ai models."); } console.log('entered flux'); const result = await client_1.fal.run("fal-ai/flux-pro", { input: { prompt, image_size: { width, height, }, num_inference_steps, guidance_scale, }, }); console.log('received data', JSON.stringify(result, null, 2)); // Extract image data from result - handle fal.ai response structure const resultData = result.data || result; const images = resultData.images || (resultData.image ? [resultData.image] : [resultData]); // Save each generated image const savedImages = []; for (let i = 0; i < images.length; i++) { const imageData = images[i]; console.log('Processing image data:', JSON.stringify(imageData, null, 2)); // Get the URL - handle different response structures let imageUrl; if (typeof imageData === 'string') { imageUrl = imageData; } else if (imageData.url) { imageUrl = imageData.url; } else if (resultData.url) { imageUrl = resultData.url; } else { throw new Error(`No URL found in image data: ${JSON.stringify(result)}`); } // Fetch the image from URL const imgResponse = await fetch(imageUrl); const buffer = await imgResponse.arrayBuffer(); // Determine filename const filename = images.length > 1 ? `${image_name}_${i + 1}.png` : `${image_name}.png`; const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; const filepath = path_1.default.join(folder, filename); await promises_1.default.access(folder, promises_1.default.constants.W_OK); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(buffer)); savedImages.push(filename); } return { content: [ { type: "text", text: `Successfully generated and saved ${images.length} image(s) with Flux Pro using prompt: "${prompt}"`, }, { type: "text", text: `Saved to: ${savedImages.join(', ')}`, }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error generating image with Flux Pro: ${error instanceof Error ? error.message : String(error)}`, }, { type: "text", text: `Stack trace: ${error instanceof Error ? error.stack : 'No stack trace available'}`, }, ], isError: true, }; } } else if (toolName === "flux_max_image_generation") { const { prompt, project_folder, image_name: rawImageName, guidance_scale = 3.5, aspect_ratio = "1:1", safety_tolerance = "2" } = request.params.arguments; const image_name = removeFileExtension(rawImageName); try { if (!FAL_API_KEY) { throw new Error("FAL_API_KEY environment variable is not set. Please set it to use fal.ai models."); } const result = await client_1.fal.run("fal-ai/flux-pro/kontext/max/text-to-image", { input: { prompt, guidance_scale, num_images: 1, safety_tolerance, output_format: "jpeg", aspect_ratio, }, }); console.log('Flux Max result:', JSON.stringify(result, null, 2)); // Extract image data from result - handle different response structures let images = []; if (result.data?.images) { images = result.data.images; } else if (result.images) { images = result.images; } else if (result.image) { images = [result.image]; } else if (result.url) { images = [{ url: result.url }]; } else { console.error('Unexpected result structure:', JSON.stringify(result, null, 2)); throw new Error(`Unexpected response structure from Flux Max: ${JSON.stringify(result)}`); } // Save each generated image const savedImages = []; for (let i = 0; i < images.length; i++) { const imageData = images[i]; // Get the URL - handle different response structures let imageUrl; if (typeof imageData === 'string') { imageUrl = imageData; } else if (imageData.url) { imageUrl = imageData.url; } else { throw new Error(`No URL found in image data at index ${i}: ${JSON.stringify(imageData)}`); } // Fetch the image from URL const imgResponse = await fetch(imageUrl); const buffer = await imgResponse.arrayBuffer(); // Determine filename const filename = images.length > 1 ? `${image_name}_${i + 1}.jpg` : `${image_name}.jpg`; const folder = FS_PREFIX ? path_1.default.join(FS_PREFIX, project_folder) : project_folder; const filepath = path_1.default.join(folder, filename); await promises_1.default.access(folder, promises_1.default.constants.W_OK); // Save the image await promises_1.default.writeFile(filepath, Buffer.from(buffer));