About Image Processor Mcp
Image Processor MCP is an image-processing server for AI assistants, covering downloads, compression, batch directory processing, filename synchronization, and local OCR.
Key features
- 01Download images by URL or Pexels search
- 02Compress and convert image formats
- 03Batch-process directories with reports
- 04Normalize filenames and sync references
- 05Extract text with local OCR
Use cases
- 01Download and optimize images for a project
- 02Convert and resize individual images
- 03Compress image folders while preserving structure
- 04Extract text from images locally
MCP support
It exposes image download, compression, batch processing, filename synchronization, and OCR tools to MCP clients.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
Tools
6Saved tool list · updated 3w ago
| Parameter | Type | Required | Description |
|---|---|---|---|
| url | string | No | URL of the image to download (required if query not provided) |
| query | string | No | Search keyword to find an image (required if url not provided) |
| outputPath | string | No | Path where to save the downloaded image |
| outputFormat | string | No | Output format: webp, avif, jpeg, png, tiff, gif (default: keep original format) |
| quality | number | No | Compression quality 1-100 (default: 85) |
| lossless | boolean | No | Use lossless compression (default: false) |
| effort | number | No | CPU effort 0-6, 6 is slowest with best compression (default: 6) |
| width | number | No | Exact target width (maintains aspect ratio if only width is specified) |
| height | number | No | Exact target height (maintains aspect ratio if only height is specified) |
| maxDimension | number | No | Auto-resize if width or height exceeds this threshold (default: 2000) |
| recursiveCompress | boolean | No | Re-compress until file is under target size (default: false) |
| expectedSizeKB | number | No | Target file size in KB for recursive compression (default: 100) |
| qualityStepDown | number | No | Quality decrease per recursive iteration (default: 10) |
| minimumQualityFloor | number | No | Minimum quality allowed during recursive compression (default: 10) |
Details
From the project READMEImage Processor MCP — Complete Documentation
Version: 0.2.3
Package: image-processor-mcp
Author: Dhvanil Pansuriya
Table of Contents
- Overview
- Installation & Configuration
- Tool: download_image
- Tool: compress_image
- Tool: compress_directory
- Tool: sync_filenames
- Tool: extract_text
- Tool: get_acknowledgement
- Configuration Defaults
- Supported Formats
- 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_KEYis optional — only needed for keyword search indownload_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_KEYnot 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: falsewith 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:
- File exists + valid JSON
- Mappings array present and non-empty
- Each entry has
oldFileNameandnewFileName(strings) - Timestamp extracted from filename
mappings-(.+).json - 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/ |
| 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
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.