Skip to main content
Glama
nickbaumann98

EverArt Forge MCP Server

generate_image

Create images for web projects using AI models, supporting multiple formats including SVG, PNG, JPG, and WebP with responsive design integration.

Instructions

Generate images using EverArt Models, optimized for web development. Supports web project paths, responsive formats, and inline preview. Available models:

  • 5000:FLUX1.1: Standard quality

  • 9000:FLUX1.1-ultra: Ultra high quality

  • 6000:SD3.5: Stable Diffusion 3.5

  • 7000:Recraft-Real: Photorealistic style

  • 8000:Recraft-Vector: Vector art style (SVG format)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
promptYesText description of desired image
modelNoModel ID (5000:FLUX1.1, 9000:FLUX1.1-ultra, 6000:SD3.5, 7000:Recraft-Real, 8000:Recraft-Vector)5000
formatNoOutput format (svg, png, jpg, webp). Note: Vector format (svg) is only available with Recraft-Vector (8000) model.svg
output_pathNoOptional: Custom output path for the generated image. If not provided, image will be saved in the default storage directory.
web_project_pathNoPath to web project root folder for storing images in appropriate asset directories.
project_typeNoWeb project type to determine appropriate asset directory structure (e.g., 'react', 'vue', 'html', 'next').
asset_pathNoOptional subdirectory within the web project's asset structure for storing generated images.
image_countNoNumber of images to generate

Implementation Reference

  • src/index.ts:365-418 (registration)
    Registration of the 'generate_image' tool in the ListToolsRequestSchema handler, including name, description, and detailed inputSchema for parameters like prompt, model, format, paths, etc.
    {
      name: "generate_image",
      description:
        "Generate images using EverArt Models, optimized for web development. " +
        "Supports web project paths, responsive formats, and inline preview. " +
        "Available models:\n" +
        "- 5000:FLUX1.1: Standard quality\n" +
        "- 9000:FLUX1.1-ultra: Ultra high quality\n" +
        "- 6000:SD3.5: Stable Diffusion 3.5\n" +
        "- 7000:Recraft-Real: Photorealistic style\n" +
        "- 8000:Recraft-Vector: Vector art style (SVG format)",
      inputSchema: {
        type: "object",
        properties: {
          prompt: {
            type: "string",
            description: "Text description of desired image",
          },
          model: {
            type: "string",
            description:
              "Model ID (5000:FLUX1.1, 9000:FLUX1.1-ultra, 6000:SD3.5, 7000:Recraft-Real, 8000:Recraft-Vector)",
            default: "5000",
          },
          format: {
            type: "string",
            description: "Output format (svg, png, jpg, webp). Note: Vector format (svg) is only available with Recraft-Vector (8000) model.",
            default: "svg"
          },
          output_path: {
            type: "string",
            description: "Optional: Custom output path for the generated image. If not provided, image will be saved in the default storage directory.",
          },
          web_project_path: {
            type: "string",
            description: "Path to web project root folder for storing images in appropriate asset directories.",
          },
          project_type: {
            type: "string",
            description: "Web project type to determine appropriate asset directory structure (e.g., 'react', 'vue', 'html', 'next').",
          },
          asset_path: {
            type: "string",
            description: "Optional subdirectory within the web project's asset structure for storing generated images.",
          },
          image_count: {
            type: "number",
            description: "Number of images to generate",
            default: 1,
          },
        },
        required: ["prompt"],
      },
    },
  • Main handler logic for 'generate_image': validates inputs, creates generation via EverArt client API, polls for completion, saves image using saveImage helper, opens in viewer, and returns formatted success response with inline details.
    case "generate_image": {
      try {
        const args = request.params.arguments as any;
        
        // Validate required parameters
        if (!args.prompt || typeof args.prompt !== 'string' || args.prompt.trim() === '') {
          return errorResponse({
            type: EverArtErrorType.VALIDATION_ERROR,
            message: "Prompt is required and must be a non-empty string."
          });
        }
        
        const prompt = args.prompt;
        // Use 'let' instead of 'const' for model since we might need to modify it
        let modelInput = args.model || "5000";
        const image_count = args.image_count || 1;
        const output_path = args.output_path;
        const web_project_path = args.web_project_path;
        const project_type = args.project_type;
        const asset_path = args.asset_path;
        
        // Enhanced validation
        if (image_count < 1 || image_count > 10) {
          return errorResponse({
            type: EverArtErrorType.VALIDATION_ERROR,
            message: "image_count must be between 1 and 10"
          });
        }
        
        // Validate model - extract the numeric ID if a combined format was provided
        const validModels = ["5000", "6000", "7000", "8000", "9000"];
        
        // Handle model IDs in the format "8000:Recraft-Vector"
        if (modelInput.includes(":")) {
          const originalModel = modelInput;
          modelInput = modelInput.split(":")[0];
          console.log(`Received combined model ID format: ${originalModel}, using base ID: ${modelInput}`);
        }
        
        if (!validModels.includes(modelInput)) {
          return errorResponse({
            type: EverArtErrorType.VALIDATION_ERROR,
            message: `Invalid model ID: ${modelInput}. Valid models are: ${validModels.join(", ")}`
          });
        }
        
        // Now we have the validated model ID
        const format = args.format || (modelInput === "8000" ? "svg" : "png");
        
        // Validate format
        const supportedFormats = ["svg", "png", "jpg", "jpeg", "webp"];
        if (!supportedFormats.includes(format.toLowerCase())) {
          return errorResponse({
            type: EverArtErrorType.VALIDATION_ERROR,
            message: `Unsupported format: ${format}. Supported formats are: ${supportedFormats.join(", ")}`
          });
        }
        
        // Validate model/format compatibility
        if (!validateModelFormatCompatibility(modelInput, format)) {
          return errorResponse({
            type: EverArtErrorType.VALIDATION_ERROR,
            message: `Format '${format}' is not compatible with model '${modelInput}'. SVG format is only available with Recraft-Vector (8000) model.`
          });
        }
        
        // Generate image with retry logic
        let generation;
        let retryCount = 0;
        
        while (retryCount < MAX_RETRIES) {
          try {
            generation = await client.v1.generations.create(
              modelInput,
              prompt,
              "txt2img",
              {
                imageCount: image_count,
                height: 1024,
                width: 1024,
                // Add extra fields for specific models if needed
                ...(modelInput === "8000" ? { variant: "vector" } : {}),
              },
            );
            break;
          } catch (error) {
            if (retryCount >= MAX_RETRIES - 1) throw error;
            
            // Exponential backoff
            const delay = INITIAL_RETRY_DELAY * Math.pow(2, retryCount);
            await new Promise(r => setTimeout(r, delay));
            retryCount++;
          }
        }
        
        if (!generation) {
          throw new Error("Failed to create generation after multiple attempts");
        }
    
        // Enhanced polling with better timeout handling
        const completedGen = await client.v1.generations.fetchWithPolling(
          generation[0].id,
          { 
            maxAttempts: 30,  // Increased from default
            interval: 3000    // Check every 3 seconds
          }
        );
    
        const imgUrl = completedGen.image_url;
        if (!imgUrl) {
          throw new Error("No image URL in the completed generation");
        }
    
        // Save image locally with specified format and path
        const filepath = await saveImage(
          imgUrl, 
          prompt, 
          modelInput, 
          format, 
          output_path, 
          web_project_path, 
          project_type, 
          asset_path
        );
    
        // Open in default viewer
        try {
          await open(filepath);
        } catch (openError) {
          console.warn("Could not open the image in default viewer:", openError);
          // Continue without throwing - this is a non-critical error
        }
    
        // Model name mapping for user-friendly display
        const modelNames: Record<string, string> = {
          "5000": "FLUX1.1 (Standard quality)",
          "9000": "FLUX1.1-ultra (Ultra high quality)",
          "6000": "Stable Diffusion 3.5",
          "7000": "Recraft-Real (Photorealistic)",
          "8000": "Recraft-Vector (Vector art)"
        };
    
        // Read the image file for inline display
        let imageData: string | undefined;
        try {
          const imageContent = await fs.readFile(filepath);
          imageData = imageContent.toString('base64');
        } catch (error) {
          console.warn("Unable to read image for inline display:", error);
          // Continue without inline display if reading fails
        }
    
        // Calculate relative web path if applicable
        let webRelativePath: string | undefined;
        if (web_project_path && filepath.startsWith(web_project_path)) {
          webRelativePath = filepath.slice(web_project_path.length);
          if (!webRelativePath.startsWith('/')) webRelativePath = '/' + webRelativePath;
        }
    
        return {
          content: [
            {
              type: "text", 
              text: `✅ Image generated and saved successfully!\n\n` +
                   `Generation details:\n` +
                   `• Model: ${modelNames[modelInput] || modelInput}\n` +
                   `• Prompt: "${prompt}"\n` +
                   `• Format: ${format.toUpperCase()}\n` +
                   `• Saved to: ${filepath}` +
                   (webRelativePath ? `\n• Web relative path: ${webRelativePath}` : ``)
            },
            {
              type: "text",
              text: `View the image at: file://${filepath}`
            }
          ],
        };
      } catch (error: unknown) {
        console.error("Detailed error:", error);
        
        // Categorize errors for better user feedback
        if (error instanceof Error) {
          if (error.message.includes("SVG format")) {
            return errorResponse({
              type: EverArtErrorType.FORMAT_ERROR,
              message: error.message
            });
          } else if (error.message.includes("Failed to fetch image")) {
            return errorResponse({
              type: EverArtErrorType.NETWORK_ERROR,
              message: "Failed to download the generated image. Please check your internet connection and try again."
            });
          } else if (error.message.includes("rate limit")) {
            return errorResponse({
              type: EverArtErrorType.API_ERROR,
              message: "EverArt API rate limit reached. Please try again later."
            });
          } else if (error.message.includes("unauthorized") || error.message.includes("authentication")) {
            return errorResponse({
              type: EverArtErrorType.AUTHENTICATION_ERROR,
              message: "API authentication failed. Please check your EverArt API key."
            });
          }
        }
        
        // Generic error handling
        const errorMessage = error instanceof Error ? error.message : "Unknown error";
        return errorResponse({
          type: EverArtErrorType.UNKNOWN_ERROR,
          message: errorMessage
        });
      }
    }
  • Key helper function saveImage: fetches image from URL, handles format-specific processing (SVG optimization, raster conversion with sharp), supports web project paths, retries on fetch errors, and saves to filesystem.
    async function saveImage(imageUrl: string, prompt: string, model: string, format: string = "svg", outputPath?: string, webProjectPath?: string, projectType?: string, assetPath?: string): Promise<string> {
      // Validate format
      format = format.toLowerCase();
      const supportedFormats = ['svg', 'png', 'jpg', 'jpeg', 'webp'];
      if (!supportedFormats.includes(format)) {
        throw new Error(`Unsupported format: ${format}. Supported formats are: ${supportedFormats.join(', ')}`);
      }
      
      // Validate model/format compatibility
      if (!validateModelFormatCompatibility(model, format)) {
        throw new Error(`Format '${format}' is not compatible with model '${model}'. SVG format is only available with Recraft-Vector (8000) model.`);
      }
      
      let filepath: string;
      
      try {
        // Handle web project paths if specified
        let projectBasePath: string | undefined;
        if (webProjectPath) {
          projectBasePath = await processWebProjectPath(webProjectPath, projectType, assetPath);
        }
        
        // Generate a standardized file name for web assets
        const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
        const sanitizedPrompt = prompt.slice(0, 20).replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
        const filename = `${sanitizedPrompt}_${model}.${format}`;
        
        if (outputPath) {
          // If outputPath is provided, ensure it has the correct extension
          const ext = path.extname(outputPath);
          if (!ext) {
            // If no extension provided, append the format
            filepath = `${outputPath}.${format}`;
          } else if (ext.slice(1).toLowerCase() !== format.toLowerCase()) {
            // If extension doesn't match format, warn but use the specified format
            console.warn(`Warning: File extension ${ext} doesn't match specified format ${format}`);
            filepath = outputPath.slice(0, -ext.length) + `.${format}`;
          } else {
            filepath = outputPath;
          }
          
          // Ensure the directory exists
          await fs.mkdir(path.dirname(filepath), { recursive: true });
        } else if (projectBasePath) {
          // Web project path takes precedence over default
          filepath = path.join(projectBasePath, filename);
        } else {
          // Default behavior: save to STORAGE_DIR with timestamp
          filepath = path.join(STORAGE_DIR, `${timestamp}_${model}_${sanitizedPrompt}.${format}`);
        }
    
        // Fetch the image with retries
        let response;
        let retryCount = 0;
        
        while (retryCount < MAX_RETRIES) {
          try {
            // @ts-ignore - node-fetch doesn't support timeout in RequestInit, but this works at runtime
            response = await fetch(imageUrl, { timeout: 30000 });
            if (response.ok) break;
            
            // If we got a 429 (rate limit), wait longer before retrying
            if (response.status === 429) {
              const retryAfter = parseInt(response.headers.get('retry-after') || '5', 10);
              await new Promise(r => setTimeout(r, retryAfter * 1000));
            } else {
              throw new Error(`Failed to fetch image: ${response.statusText} (${response.status})`);
            }
          } catch (error) {
            if (retryCount >= MAX_RETRIES - 1) throw error;
            
            // Exponential backoff
            const delay = INITIAL_RETRY_DELAY * Math.pow(2, retryCount);
            await new Promise(r => setTimeout(r, delay));
          }
          
          retryCount++;
        }
        
        if (!response || !response.ok) {
          throw new Error(`Failed to fetch image after ${MAX_RETRIES} attempts`);
        }
    
        const buffer = await response.arrayBuffer();
        const content = Buffer.from(buffer);
    
        if (format === "svg") {
          // For SVG, optimize and save
          const svgString = content.toString('utf-8');
          const result = optimize(svgString, {
            multipass: true,
            plugins: [
              'preset-default',
              'removeDimensions',
              'removeViewBox',
              'cleanupIds',
            ],
          });
          await fs.writeFile(filepath, result.data);
        } else {
          // For raster formats, convert using sharp with better error handling
          try {
            const image = sharp(content);
            switch (format.toLowerCase()) {
              case "png":
                await image.png({ quality: 90 }).toFile(filepath);
                break;
              case "jpg":
              case "jpeg":
                await image.jpeg({ quality: 90 }).toFile(filepath);
                break;
              case "webp":
                await image.webp({ quality: 90 }).toFile(filepath);
                break;
              default:
                throw new Error(`Unsupported format: ${format}`);
            }
          } catch (error) {
            throw new Error(`Image processing failed: ${(error as Error).message}`);
          }
        }
    
        return filepath;
      } catch (error) {
        throw new Error(`Failed to save image: ${(error as Error).message}`);
      }
    }
  • Helper function to validate model-format compatibility, specifically restricting SVG to Recraft-Vector model (8000).
    function validateModelFormatCompatibility(model: string, format: string): boolean {
      // SVG is only supported by Recraft-Vector (8000)
      if (format.toLowerCase() === 'svg' && model !== '8000') {
        return false;
      }
      return true;
    }
  • Input schema definition for generate_image tool, specifying properties, descriptions, defaults, and required fields.
    type: "object",
    properties: {
      prompt: {
        type: "string",
        description: "Text description of desired image",
      },
      model: {
        type: "string",
        description:
          "Model ID (5000:FLUX1.1, 9000:FLUX1.1-ultra, 6000:SD3.5, 7000:Recraft-Real, 8000:Recraft-Vector)",
        default: "5000",
      },
      format: {
        type: "string",
        description: "Output format (svg, png, jpg, webp). Note: Vector format (svg) is only available with Recraft-Vector (8000) model.",
        default: "svg"
      },
      output_path: {
        type: "string",
        description: "Optional: Custom output path for the generated image. If not provided, image will be saved in the default storage directory.",
      },
      web_project_path: {
        type: "string",
        description: "Path to web project root folder for storing images in appropriate asset directories.",
      },
      project_type: {
        type: "string",
        description: "Web project type to determine appropriate asset directory structure (e.g., 'react', 'vue', 'html', 'next').",
      },
      asset_path: {
        type: "string",
        description: "Optional subdirectory within the web project's asset structure for storing generated images.",
      },
      image_count: {
        type: "number",
        description: "Number of images to generate",
        default: 1,
      },
    },
    required: ["prompt"],
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It lists available models and hints at web project integration but fails to describe critical behaviors like authentication needs, rate limits, error handling, or what happens when images are generated (e.g., saved to disk, returned as data). For a complex 8-parameter tool with no annotation coverage, this is a significant gap.

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

Conciseness4/5

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

The description is efficiently structured with a clear opening sentence and a bulleted list of models. However, the model list could be more concise (e.g., by grouping similar models), and some sentences like 'Supports web project paths, responsive formats, and inline preview' are vague and could be tightened.

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

Completeness2/5

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

Given the tool's complexity (8 parameters, no annotations, no output schema), the description is incomplete. It lacks details on return values (e.g., image URLs, file paths), error conditions, web development integration specifics, and behavioral constraints. For a generative tool with multiple parameters, this leaves significant gaps for an AI agent.

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

Parameters3/5

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

Schema description coverage is 100%, so the schema already documents all 8 parameters thoroughly. The description adds some value by listing model options with quality/style notes, but it doesn't provide additional semantic context beyond what's in the schema (e.g., explaining 'web_project_path' integration or 'project_type' implications). Baseline 3 is appropriate when the schema does the heavy lifting.

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

Purpose4/5

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

The description clearly states the tool generates images using EverArt Models and specifies it's optimized for web development, which provides a specific verb+resource. However, it doesn't explicitly distinguish this from sibling tools like 'list_images' or 'view_image' beyond the core generation function, preventing a perfect score.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives like 'list_images' or 'view_image'. It mentions web development optimization but doesn't specify scenarios where this is preferred over other image-related tools or when not to use it, leaving usage context implied at best.

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

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/nickbaumann98/everart-forge-mcp'

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