Skip to main content
Glama

process_folder

Process all images in a folder by converting formats, resizing dimensions, or both operations simultaneously while automatically skipping non-image files.

Instructions

Apply a convert, resize, or convert-and-resize operation to all image files in a folder. Non-image files are automatically skipped.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
folder_pathYesAbsolute path to the folder containing images
operationYesOperation to apply to every image
output_formatNoTarget format (required for convert / convert_and_resize)
qualityNoJPEG/WebP quality (1-100, default 90)
widthNoTarget width in pixels
heightNoTarget height in pixels
presetNoNamed size preset (overrides width/height)
lock_aspect_ratioNoKeep aspect ratio when resizing (default true)
output_folderNoWhere to save the processed files (defaults to the same folder as input)

Implementation Reference

  • The implementation of the 'process_folder' MCP tool. It iterates over files in a given directory, filters them to include only images, and performs the specified image transformation (convert, resize, or convert_and_resize) on each.
    server.tool(
      "process_folder",
      "Apply a convert, resize, or convert-and-resize operation to all image files in a folder. Non-image files are automatically skipped.",
      {
        folder_path: z.string().describe("Absolute path to the folder containing images"),
        operation: z.enum(["convert", "resize", "convert_and_resize"]).describe("Operation to apply to every image"),
        output_format: z.enum(["png", "jpeg", "gif", "webp", "ico"]).optional().describe("Target format (required for convert / convert_and_resize)"),
        quality: z.number().int().min(1).max(100).optional().describe("JPEG/WebP quality (1-100, default 90)"),
        width: z.number().int().positive().optional().describe("Target width in pixels"),
        height: z.number().int().positive().optional().describe("Target height in pixels"),
        preset: z.enum([
          "instagram-square", "instagram-portrait", "instagram-landscape",
          "twitter-post", "twitter-header", "full-hd", "4k",
          "youtube-thumbnail", "favicon",
        ]).optional().describe("Named size preset (overrides width/height)"),
        lock_aspect_ratio: z.boolean().optional().default(true).describe("Keep aspect ratio when resizing (default true)"),
        output_folder: z.string().optional().describe("Where to save the processed files (defaults to the same folder as input)"),
      },
      async ({ folder_path, operation, output_format, quality = 90, width, height, preset, lock_aspect_ratio = true, output_folder }) => {
        try {
          const stat = await fs.stat(folder_path);
          if (!stat.isDirectory()) {
            return { isError: true, content: [{ type: "text", text: `Error: ${folder_path} is not a directory.` }] };
          }
    
          const entries = await fs.readdir(folder_path);
          const imageFiles = entries.filter(isImageFile);
    
          if (imageFiles.length === 0) {
            return { content: [{ type: "text", text: JSON.stringify({ success: true, processed: 0, skipped: entries.length, results: [] }) }] };
          }
    
          const outDir = output_folder ?? folder_path;
          if (output_folder) {
            await fs.mkdir(output_folder, { recursive: true });
          }
    
          let targetW = width;
          let targetH = height;
          if (preset) {
            targetW = PRESETS[preset].width;
            targetH = PRESETS[preset].height;
          }
    
          const results = [];
    
          for (const filename of imageFiles) {
            const inputPath = path.join(folder_path, filename);
            try {
              let outPath;
    
              if (operation === "convert") {
                if (!output_format) throw new Error("output_format is required for convert operation");
                const outExt = output_format === "jpeg" ? "jpg" : output_format;
                const base = path.basename(filename, path.extname(filename));
                outPath = path.join(outDir, `${base}.${outExt}`);
    
                if (output_format === "ico") {
                  const icoBuffer = await encodeIco(inputPath);
                  await fs.writeFile(outPath, icoBuffer);
                } else {
                  await sharp(inputPath).toFormat(output_format, { quality }).toFile(outPath);
                }
    
              } else if (operation === "resize") {
                if (!targetW && !targetH) throw new Error("Provide width, height, or a preset for resize operation");
                const ext = path.extname(filename).slice(1).toLowerCase() || "jpg";
                outPath = path.join(outDir, filename);
                const fit = lock_aspect_ratio ? "inside" : "fill";
                await sharp(inputPath).resize(targetW, targetH, { fit }).toFile(outPath);
    
              } else { // convert_and_resize
                if (!output_format) throw new Error("output_format is required for convert_and_resize operation");
                const outExt = output_format === "jpeg" ? "jpg" : output_format;
                const base = path.basename(filename, path.extname(filename));
                outPath = path.join(outDir, `${base}.${outExt}`);
    
                if (output_format === "ico") {
                  const icoBuffer = await encodeIco(inputPath);
                  await fs.writeFile(outPath, icoBuffer);
                } else {
                  let pipeline = sharp(inputPath);
                  if (targetW || targetH) {
                    const fit = lock_aspect_ratio ? "inside" : "fill";
                    pipeline = pipeline.resize(targetW, targetH, { fit });
                  }
                  await pipeline.toFormat(output_format, { quality }).toFile(outPath);
                }
              }
    
              const outStat = await fs.stat(outPath);
              results.push({ file: filename, output_path: outPath, size_bytes: outStat.size, success: true });
            } catch (fileErr) {
              results.push({ file: filename, success: false, error: fileErr.message });
            }
          }
    
          const succeeded = results.filter(r => r.success).length;
          const failed = results.filter(r => !r.success).length;
          return {
            content: [{ type: "text", text: JSON.stringify({
              success: true,
              processed: succeeded,
              failed,
              skipped: entries.length - imageFiles.length,
              results,
            }) }],
          };
        } catch (err) {
          return { isError: true, content: [{ type: "text", text: `Error: ${err.message}` }] };
        }
      }
    );

Tool Definition Quality

Score is being calculated. Check back soon.

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/sonic0002/imagic-mcp'

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