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}` }] };
        }
      }
    );

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