# Image Processor MCP — Complete Documentation **Version**: 0.2.3 **Package**: `image-processor-mcp` **Author**: Dhvanil Pansuriya --- ## Table of Contents 1. [Overview](#overview) 2. [Installation & Configuration](#installation--configuration) 3. [Tool: download_image](#1-download_image) 4. [Tool: compress_image](#2-compress_image) 5. [Tool: compress_directory](#3-compress_directory) 6. [Tool: sync_filenames](#4-sync_filenames) 7. [Tool: extract_text](#5-extract_text) 8. [Tool: get_acknowledgement](#6-get_acknowledgement) 9. [Configuration Defaults](#configuration-defaults) 10. [Supported Formats](#supported-formats) 11. [Credits & License](#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 ```bash 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 ```bash npm install image-processor-mcp ``` ### Requirements - **Node.js 18** or higher ### MCP Configuration #### Claude Desktop ```json { "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) ```json { "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/](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) ```json { "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 ```json { "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 ```json { "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) ```json { "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 ```ts 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://github.com/pansuriyadhvanil/) | | LinkedIn | [https://linkedin.com/in/dhvanil-pansuriya/](https://linkedin.com/in/dhvanil-pansuriya/) | | Package | `image-processor-mcp` on npm | ### Technology Credits | Technology | Role | |------------|------| | [Model Context Protocol SDK](https://github.com/modelcontextprotocol/typescript-sdk) | MCP server framework | | [sharp](https://sharp.pixelplumbing.com/) | High-performance image processing | | [tesseract.js](https://github.com/naptha/tesseract.js/) | OCR text extraction — 100% local, no API key | | [Pexels](https://www.pexels.com/) | Free stock photo API for image search | | [axios](https://axios-http.com/) | HTTP client | | [fs-extra](https://github.com/jprichardson/node-fs-extra) | Extended file system | ### License **MIT License** — See [LICENSE](https://github.com/pansuriyadhvanil/Image-Processor-MCP/blob/main/LICENSE) for details. # Image-Processor-MCP