npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

image-processor-mcp

v0.2.3

Published

MCP server for downloading, compressing, optimizing images, OCR text extraction, and batch processing with auto-generated reports. Supports WebP, AVIF, JPEG, PNG, and more.

Readme

Image Processor MCP — Complete Documentation

Version: 0.2.3
Package: image-processor-mcp
Author: Dhvanil Pansuriya


Table of Contents

  1. Overview
  2. Installation & Configuration
  3. Tool: download_image
  4. Tool: compress_image
  5. Tool: compress_directory
  6. Tool: sync_filenames
  7. Tool: extract_text
  8. Tool: get_acknowledgement
  9. Configuration Defaults
  10. Supported Formats
  11. Credits & License

Overview

An MCP (Model Context Protocol) server that provides 6 tools for image downloading, compression, batch processing, filename synchronization, OCR text extraction, and configuration discovery.

Quick Start

npx -y image-processor-mcp

Key Features

  • Download images from URLs or search by keyword (Pexels integration)
  • Compress and convert single images with 10 output formats
  • Recursive compression to target file size
  • Lossless and lossy compression with configurable quality and CPU effort
  • Auto-resize oversized images
  • Batch process entire directories preserving folder structure
  • Filename and directory name normalization
  • Auto-generated markdown compression reports
  • Auto-generated filename mappings paired with reports
  • Scan project files and replace old filenames with new ones
  • OCR text extraction (100+ languages, runs 100% locally)

Installation & Configuration

Install from npm

npm install image-processor-mcp

Requirements

  • Node.js 18 or higher

MCP Configuration

Claude Desktop

{
  "mcpServers": {
    "image-processor-mcp": {
      "command": "npx",
      "args": ["-y", "image-processor-mcp"],
      "env": {
        "IMAGE_SEARCH_API_KEY": "your_pexels_api_key_here"
      }
    }
  }
}

VS Code (Cline / MCP extensions)

{
  "mcpServers": {
    "image-processor-mcp": {
      "command": "npx",
      "args": ["-y", "image-processor-mcp"],
      "env": {
        "IMAGE_SEARCH_API_KEY": "your_pexels_api_key_here"
      }
    }
  }
}

IMAGE_SEARCH_API_KEY is optional — only needed for keyword search in download_image. Get a free key at https://www.pexels.com/api/key/.


1. download_image

Downloads an image from a URL or searches by keyword. Optionally compress/convert the image.

Modes

| Mode | Parameter | Description | |------|-----------|-------------| | URL mode | url | Direct download from an image URL | | Search mode | query | Keyword search via Pexels |

If both provided, search mode takes priority.

Parameters

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | url | string | no* | — | Direct image URL | | query | string | no* | — | Search keyword | | outputPath | string | yes | — | Save path | | outputFormat | string | no | original | webp, avif, jpeg, png, tiff, gif | | quality | number | no | 85 | 1–100 | | lossless | boolean | no | false | Lossless mode | | effort | number | no | 6 | CPU effort 0–6 | | width | number | no | — | Target width | | height | number | no | — | Target height | | maxDimension | number | no | 2000 | Auto-resize threshold | | recursiveCompress | boolean | no | false | Iterative size targeting | | expectedSizeKB | number | no | 100 | Target KB | | qualityStepDown | number | no | 10 | Per-iteration decrease | | minimumQualityFloor | number | no | 10 | Minimum quality |

* One of url or query is required.

Data Flow

Request → type guard → Controller
  ├─ Search mode? → Pexels API → first hit URL
  └─ URL mode?    → use directly
  ↓
axios.get(url, arraybuffer, 30s timeout)
  ↓
Write to temp file → check compression params
  ├─ YES → ImageService.compressImage → clean temp → return result
  └─ NO  → move to final path → return success

Response Example (search + compression)

{
  "query": "mountain sunset",
  "imageId": 12345,
  "width": 1920,
  "height": 1080,
  "imageSize": 0,
  "user": "John Doe",
  "tags": "mountain sunset",
  "inputPath": "...",
  "outputPath": "./images/mountain.webp",
  "originalSize": 500000,
  "compressedSize": 80000,
  "compressionRatio": 84.00,
  "success": true,
  "iterations": 1,
  "qualitySteps": [85],
  "finalQuality": 85,
  "exceededTarget": false
}

Edge Cases

  • No url or query: Error — must provide one
  • Empty search results: Error — no images found
  • Missing API key: Error — IMAGE_SEARCH_API_KEY not set
  • Download timeout (30s): Error — timeout exceeded
  • Compression fails: Temp file cleaned up, error returned

2. compress_image

Compress or convert a single image with full control over quality, dimensions, format, and recursive compression.

Parameters

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | inputPath | string | yes | — | Source image path | | outputPath | string | yes | — | Destination path | | outputFormat | string | no | webp | Output format | | quality | number | no | 85 | 1–100 | | lossless | boolean | no | false | Lossless mode | | effort | number | no | 6 | CPU effort 0–6 | | width | number | no | — | Target width | | height | number | no | — | Target height | | maxDimension | number | no | 2000 | Auto-resize threshold | | recursiveCompress | boolean | no | false | Iterative targeting | | expectedSizeKB | number | no | 100 | Target KB | | qualityStepDown | number | no | 10 | Per-iteration decrease | | minimumQualityFloor | number | no | 10 | Minimum quality |

Core Compression Algorithm

1. Read original size + image metadata (sharp)
2. Compute resize:
   - Explicit w/h? → use those
   - Exceeds maxDimension? → scale proportionally
   - Otherwise → no resize
3. Loop:
   - sharp pipeline → apply format with quality
   - If input === output → buffer (safe in-place)
   - Else → write to file
   - If recursive + over target + above floor → reduce quality, loop
   - Else → break
4. Return CompressionResult

Format Conversion

| Format | Sharp Options | |--------|---------------| | webp | {quality, lossless, effort, smartSubsample, alphaQuality:90} | | avif | {quality, lossless, effort} | | jpeg | {quality, mozjpeg:true} | | png | {compressionLevel:9} | | tiff | {quality, compression:'lzw'} | | gif | gif() (no options) | | other | Falls back to WebP |

Response Example

{
  "inputPath": "./input/photo.jpg",
  "outputPath": "./output/photo.webp",
  "originalSize": 500000,
  "compressedSize": 80000,
  "compressionRatio": 84.00,
  "success": true,
  "iterations": 1,
  "qualitySteps": [85],
  "finalQuality": 85,
  "exceededTarget": false
}

Edge Cases

  • In-place compression (same path): Buffer-based to avoid corruption
  • Missing input file: success: false with error
  • Unknown format: Falls back to WebP
  • Recursion floor: Stops when quality can't decrease further
  • Abort: Returns partial result with success: false

3. compress_directory

Batch compress all images in a directory recursively, preserving folder structure. Generates report + mappings.

Parameters

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | inputDir | string | yes | — | Source directory | | outputDir | string | yes | — | Output directory | | outputFormat | string | no | webp | Output format | | quality | number | no | 85 | 1–100 | | lossless | boolean | no | false | Lossless mode | | effort | number | no | 6 | CPU effort 0–6 | | maxDimension | number | no | 2000 | Auto-resize threshold | | normalizeFilename | boolean | no | false | Clean filenames | | filenameReplaceChars | string | no | , -, ., & | Replace chars | | filenameCase | string | no | lowercase | Case conversion | | normalizeDirname | boolean | no | false | Clean dir names | | dirnameReplaceChars | string | no | , -, ., & | Dir replace chars | | dirnameCase | string | no | lowercase | Dir case | | recursiveCompress | boolean | no | false | Iterative targeting | | expectedSizeKB | number | no | 100 | Target KB | | qualityStepDown | number | no | 5 (schema) / 10 (code) | Per-iteration decrease | | minimumQualityFloor | number | no | 10 | Minimum quality |

Data Flow

Request → type guard → Controller
  ├─ Validate input directory exists
  ├─ Apply defaults, build FileNamingOptions
  ├─ Scan all image files (recursive)
  ├─ Filter out SVG (restricted)
  ├─ For each file:
  │   ├─ Compute output path (preserves structure, normalizes, changes ext)
  │   └─ Run ImageService.compressImage
  ├─ Generate report (.image-processor-mcp/reports/)
  └─ Generate mappings JSON (.image-processor-mcp/mappings/)

Output Path Algorithm

relativePath = inputDir → inputFile difference

1. Dir naming enabled → normalize each directory component
2. File naming enabled → normalize basename
3. Replace extension with .{outputFormat}
4. Prepend outputDir

normalizeName

Replace each char → '_', collapse multiple '_', trim edges, apply case

Generated Files

.image-processor-mcp/
├── reports/scan-report-{timestamp}.md
└── mappings/mappings-{timestamp}.json

Response Example

{
  "success": true,
  "totalFiles": 10,
  "successful": 9,
  "failed": 1,
  "totalOriginalSize": 5000000,
  "totalCompressedSize": 1200000,
  "overallReduction": 76.00,
  "durationMs": 5432,
  "reportPath": ".image-processor-mcp/reports/scan-report-2026-05-29-15-30-00.md",
  "mappingsPath": ".image-processor-mcp/mappings/mappings-2026-05-29-15-30-00.json"
}

Edge Cases

  • Input dir not found: Early error, no files written
  • SVG files: Silently excluded
  • Abort mid-batch: Partial results processed
  • All files fail: No mappings file generated
  • No images found: Empty report

4. sync_filenames

Scan project files and replace all references to old filenames with new filenames after compression.

Parameters

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | scanDir | string | yes | — | Directory to scan | | mappingFilePath | string | no | — | Path to mappings JSON file | | mappings | array | no | — | Inline {oldFileName, newFileName}[] | | includeExtensions | array | no | html,js,ts,css,md,json... | Extensions to scan | | excludeDirectories | array | no | node_modules,.git,build,dist... | Dirs to skip | | dryRun | boolean | no | true | Preview only |

Validation

When using mappingFilePath:

  1. File exists + valid JSON
  2. Mappings array present and non-empty
  3. Each entry has oldFileName and newFileName (strings)
  4. Timestamp extracted from filename mappings-(.+).json
  5. Matching report must exist: reports/scan-report-{timestamp}.md

If the matching report is missing → operation rejected (prevents stale mappings).

Data Flow

Request → type guard → Controller
  ├─ mappingFilePath?
  │   YES → ReferenceService.readMappingFile → validate against report
  │   NO  → use inline mappings
  ├─ dryRun = args.dryRun !== false
  ├─ ReferenceService.scanReferences:
  │   ├─ Collect files recursively (filtered by extension, excluding dirs)
  │   ├─ For each file:
  │   │   ├─ For each mapping:
  │   │   │   ├─ Regex-escape oldFileName, count occurrences
  │   │   │   ├─ Build line-by-line details
  │   │   │   ├─ Build preview (3 lines context)
  │   │   │   └─ If !dryRun: replace in content
  │   │   └─ If modified: write file
  │   └─ Return UpdateRefResult
  └─ ReportService.generateSyncReport → .image-processor-mcp/syncs/

Generated Files

.image-processor-mcp/syncs/sync-report-{timestamp}.md           (live)
.image-processor-mcp/syncs/dry-run/sync-report-{timestamp}.md   (dry-run)

Response Example

Dry run — no files were modified.

Mapping source: .image-processor-mcp/mappings/mappings-2026-05-29-15-30-00.json
Files scanned: 42
Files with matches: 3
Total replacements: 12

--- Matches ---

[src/components/Header.tsx]
  "logo.png" → "logo.webp" (5x)
  Preview: ... context lines ...
  Line 15:23  "logo.png" → "logo.webp"
  ... and 3 more (see report)

Report saved to: .image-processor-mcp/syncs/dry-run/sync-report-2026-05-29-15-31-00.md

Edge Cases

  • Mapping file not found: Error with path
  • Invalid JSON: Parse error
  • No matching report: Detailed rejection message
  • Neither source provided: Error explaining options
  • No matches found: Informational message
  • Regex special chars: Properly escaped
  • Binary files: Filtered by extension list

5. extract_text

Extract visible printed text from images using OCR (tesseract.js). Runs entirely locally.

Parameters

| Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | imagePath | string | yes | — | Path to image file | | language | string | no | eng | Language code (use + for multiple) | | rectangle | object | no | — | Region {top, left, width, height} | | outputFormat | enum | no | text | text, blocks, hocr, tsv | | preprocess | boolean | no | true | Grayscale + auto-rotate |

Data Flow

Request → type guard → OcrController → OcrService.recognize
  ├─ Validate path (exists, not dir, not empty, not SVG)
  ├─ Validate rectangle (positive dimensions)
  ├─ Build sharp pipeline:
  │   ├─ Convert unsupported formats to PNG
  │   └─ Preprocess: grayscale + rotate
  ├─ runOcr:
  │   ├─ Serialized via jobQueue (one at a time)
  │   ├─ getWorker: reuse / reinitialize / create
  │   ├─ worker.recognize()
  │   └─ Recycle worker every 500 jobs
  └─ Return ExtractTextResult

Worker Lifecycle

| State | Action | |-------|--------| | Worker exists, same language | Reuse directly | | Worker exists, different language | worker.reinitialize(language) | | No worker | createWorker(language, 1, ...) | | After 500 jobs | setImmediate(recycleWorker) | | Worker error | errorHandler nulls out reference | | SIGINT / transport close | shutdown() → terminate |

Supported Input Formats

| Natively supported | Auto-converted to PNG | |-------------------|----------------------| | bmp, jpg, jpeg, png | tiff, heic, heif, webp*, gif* | | pbm, pgm, ppm | and all others |

* webp and gif ARE natively supported by tesseract.js, despite the conversion check. The code converts any extension NOT in OCR_SUPPORTED_EXTENSIONS. Since webp and gif are in that list, they are NOT converted.

Limitations

  • Handwritten text: NOT supported
  • SVG files: Rejected (vector format)
  • Empty images: Return empty string (not an error)
  • First run: Downloads language data (~2MB) from CDN

Response Example (text format)

{
  "success": true,
  "text": "Extracted text content",
  "confidence": 92.5,
  "words": 42,
  "language": "eng",
  "durationMs": 1234,
  "preprocessed": true
}

Edge Cases

  • SVG: Explicit rejection
  • Unsupported format: Auto-converted to PNG
  • Worker crash: Auto-recovery (errorHandler nulls out)
  • Abort: DOMException caught → cancelled result
  • Empty file (0 bytes): Explicit rejection
  • Path is directory: Explicit rejection

6. get_acknowledgement

Returns comprehensive documentation of all tools, defaults, formats, and environment variables.

Parameters

None — empty input schema.

Response

A full markdown document built dynamically from all configuration constants:

| Section | Source | |---------|--------| | All 6 tools | Hardcoded descriptions | | Configuration defaults | DEFAULTS + CONFIG_DESCRIPTIONS | | Supported input formats | SUPPORTED_EXTENSIONS + INPUT_EXTENSION_DESCRIPTIONS | | Supported output formats | SUPPORTED_OUTPUT_FORMATS + OUTPUT_FORMAT_DESCRIPTIONS | | Sync scan defaults | DEFAULT_EXTENSIONS + DEFAULT_EXCLUDE_DIRS | | Restricted extensions | RESTRICTED_EXTENSIONS | | Environment variables | Hardcoded | | Credits | Hardcoded |

Implementation

ToolController.handleGetAcknowledgement()
  // Synchronous — no abort signal, no async
  // Returns { content: [{ type: 'text', text: markdown }] }

Edge Cases

None — always succeeds, no parameters, no I/O.


Configuration Defaults

| Parameter | Default | Description | |-----------|---------|-------------| | quality | 85 | Compression quality for lossy formats (1–100) | | lossless | false | Lossless compression (WebP, PNG, GIF, AVIF) | | effort | 6 | CPU effort (0–6). Higher = smaller files | | maxDimension | 2000 | Auto-resize threshold in pixels | | expectedSizeKB | 100 | Target file size for recursive compression | | qualityStepDown | 10 | Quality decrease per recursive iteration | | minimumQualityFloor | 10 | Minimum quality (1–100) during recursion | | outputFormat | webp | Default output format | | filenameCase | lowercase | Filename case conversion | | dirnameCase | lowercase | Directory name case conversion | | filenameReplaceChars | [' ', '-', '.', '&'] | Characters replaced with _ in filenames | | dirnameReplaceChars | [' ', '-', '.', '&'] | Characters replaced with _ in dir names |

Supported Formats

Input Formats (35+)

.jpg, .jpeg, .png, .gif, .bmp, .tiff, .tif, .webp, .avif, .svg, .heic, .heif, .ico, .cur, .jp2, .j2k, .jpf, .jpx, .jpm, .mj2, .jxr, .wdp, .hdp, .psd, .psb, .dds, .tga, .vda, .icb, .vst, .pbm, .pgm, .ppm, .pnm, .pfm, .exr, .hdr, .hrd, .pic, .pict, .pct, .xbm, .xpm, .wal, .cut, .ras, .sun, .sgi, .rgb, .rgba, .bw, .pcx, .pcd, .iff, .lbm

Output Formats (10)

| Format | Description | |--------|-------------| | webp | Google WebP. Excellent lossy/lossless. Supports transparency. | | avif | AV1-based. 30–50% smaller than JPEG. Slower encoding. | | jpeg | Universal compatibility. No transparency. | | png | Lossless with transparency. Best for graphics/logos. | | tiff | High-quality. Print/publishing. Largest sizes. | | gif | Animation + transparency. 256 colors. | | heif | HEVC-based. Apple ecosystem. | | jp2 | JPEG 2000. Wavelet-based. Medical/ cinema. | | jxl | JPEG XL. Next-gen. Lossless JPEG re-encoding. | | pdf | Convert images to PDF pages. |

Restricted Extensions

.svg — SVG files are skipped during batch compression (vector format — sharp cannot safely compress without breaking rendering).


Credits & License

Author

Dhvanil Pansuriya

| | | |---|---| | GitHub | https://github.com/pansuriyadhvanil/ | | LinkedIn | https://linkedin.com/in/dhvanil-pansuriya/ | | Package | image-processor-mcp on npm |

Technology Credits

| Technology | Role | |------------|------| | Model Context Protocol SDK | MCP server framework | | sharp | High-performance image processing | | tesseract.js | OCR text extraction — 100% local, no API key | | Pexels | Free stock photo API for image search | | axios | HTTP client | | fs-extra | Extended file system |

License

MIT License — See LICENSE for details.

Image-Processor-MCP