img-convert MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@img-convert MCP Serverconvert image.png to webp format"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
img-convert
Fast CLI image converter with Web UI.
Convert images between:
JPG
PNG
WebP
AVIF
GIF
TIFF
Built for developers and AI agents.
Install
npx @dutchbase/img-convertor
npm install -g @dutchbase/img-convert# Convert a file
img-convert photo.jpg -f webp --json
# Inspect an image without converting
img-convert info photo.jpg
# Give Claude Code native image conversion tools
img-convert mcpFast, scriptable image conversion powered by Sharp. Ships as an npm package, a CLI, a REST API, and a Model Context Protocol (MCP) server — making it a first-class tool for both developers and AI agents.
Related MCP server: gimp-mcp
Table of Contents
Agent Skill
img-convert ships a SKILL.md that teaches coding agents the CLI's JSON contract, exit and error codes, flags, batch manifests and task recipes. It's included in the npm package and printed by img-convert skill.
# Claude Code, all projects
mkdir -p ~/.claude/skills/img-convert && img-convert skill > ~/.claude/skills/img-convert/SKILL.md
# Claude Code, this project only
mkdir -p .claude/skills/img-convert && img-convert skill > .claude/skills/img-convert/SKILL.mdOther agents can run img-convert skill to read the guide. A test (__tests__/skill.test.ts) fails if a CLI flag or command is missing from it.
Why img-convert
Most image conversion tools are designed for interactive use — a GUI, a web form, a one-off shell command. img-convert is designed for programmatic use: CI pipelines, build scripts, AI agent workflows, and server-side processing.
Key design principles:
Machine-readable output first.
--jsonon every command.stderrcarries human-facing progress.stdoutcarries data. Every command pipes cleanly tojq.AI agent optimized. Ships a native MCP server. Claude Code, Cursor, and any MCP-compatible agent can call
convert_imageandget_image_infoas native tools — no shell escaping, no subprocess management, full type safety.Composable. CLI, Node.js API, and REST API all run the same
processImage()pipeline under the hood. Behavior is identical regardless of the call path.Minimal published footprint. The npm bundle is ~50 KB. The full Next.js web UI is excluded from the published package — only
dist/,lib/,types/, andcli/ship.
Installation
Global CLI
npm install -g @dutchbase/img-convertLocal dependency (Node.js API)
npm install @dutchbase/img-convertSelf-hosted web UI
git clone https://github.com/dutchbase/img-convert
cd img-convert
npm install
npm run dev # http://localhost:3000
npm run build # production Next.js buildRequirements
Node.js >= 20.9.0
Sharp's native bindings are pre-built for Linux x64/arm64, macOS arm64/x64, and Windows x64. For other platforms, see the Sharp installation guide.
CLI Reference
The complete reference (every flag, the JSON contract, error codes, manifest keys and recipes) lives in SKILL.md, also printed by img-convert skill. img-convert --help shows the flags with examples.
img-convert photo.jpg -f webp --json # convert one file
img-convert "shots/*.png" -f jpeg --width 200 --height 200 --fit cover -o thumbs --json # thumbnails
img-convert big.png -f jpeg --max-size 200KB --json # fit a byte budget
img-convert info "shots/*.png" # metadata as a JSON array
img-convert batch jobs.json --json # per-file settings from a manifestWith --json, stdout is always a JSON array in input order. Each item has index, ok and either the result fields or error: { code, message }.
Exit codes: 0 all ok · 1 some items failed (stdout still has the full array) · 2 usage error ({"error":{...}} on stdout with --json).
AI Agent Integration
img-convert is designed to be called directly by AI agents as a native typed tool — not as a raw shell command.
MCP Server
Model Context Protocol is the open standard for giving AI agents structured tool access. img-convert ships a production-ready MCP server.
Register with Claude Code
claude mcp add img-convert -- img-convert mcpThe server resolves file paths against, and restricts them to, its working directory.
After registering, Claude Code can call convert_image, get_image_info, batch_convert, and list_supported_formats as native tools — with full type checking, no shell escaping, and structured return values.
Register with other MCP clients
Any client that supports the MCP stdio transport works identically. Point it at img-convert mcp.
Cursor (~/.cursor/mcp.json), Continue, Zed, and any other MCP host follow the same pattern:
{
"mcpServers": {
"img-convert": {
"command": "img-convert",
"args": ["mcp"]
}
}
}MCP Tools
convert_image
Convert a single image file. Accepts file paths and URLs.
Input schema:
Parameter | Type | Required | Description |
| string | Yes | File path or HTTP/HTTPS URL |
| string | Yes | One of: |
| string | No | Output file path. Derived from |
| number | No | Quality 1–100, default |
| number | No | Resize width, maintains aspect ratio |
| number | No | Resize height, maintains aspect ratio |
| boolean | No | Strip EXIF, default |
| boolean | No | Desaturate to grayscale |
| number | No | Rotation degrees |
| string | No | Background fill color (CSS color string) |
Returns:
{
"input_path": "photo.jpg",
"output_path": "photo.webp",
"input_bytes": 204800,
"output_bytes": 81920,
"reduction": 60.0,
"width": 1920,
"height": 1080,
"format": "webp",
"quality": 85
}get_image_info
Get full metadata about an image without converting it.
Input schema:
Parameter | Type | Required | Description |
| string | Yes | File path or HTTP/HTTPS URL |
Returns:
{
"format": "jpeg",
"width": 4032,
"height": 3024,
"filesize": 3891200,
"hasAlpha": false,
"hasExif": true,
"colorSpace": "srgb",
"isAnimated": false,
"channels": 3,
"density": 72
}Use this first to make informed conversion decisions: does the image have transparency (affects JPEG conversion), is it animated (affects frame handling), what is the color space (affects print workflows)?
batch_convert
Convert multiple images in a single tool call.
Input schema:
Parameter | Type | Required | Description |
| array | Yes | Array of conversion jobs (see below) |
| number | No | Parallel limit, default |
Each item in items:
Field | Type | Required |
| string | Yes |
| string | Yes |
| string | No |
| number | No |
| number | No |
| number | No |
Returns: Array of result objects, one per input item.
list_supported_formats
Enumerate what the server can read and write.
Returns:
{
"input": ["jpeg", "png", "webp", "avif", "gif", "tiff", "heic", "svg"],
"output": ["jpeg", "png", "webp", "avif", "gif", "tiff"]
}CLI for agents
Agents that use a shell should call the CLI with --json. The contract (ordered arrays, ok per item, stable error codes, exit code 2 for usage errors) and batch manifests are documented in SKILL.md.
Node.js API
import { convert, getInfo, batch } from '@dutchbase/img-convert'All three functions accept file paths, HTTP/HTTPS URLs, or raw Buffer objects as input.
convert()
function convert(
input: string | Buffer,
options: ConvertApiOptions
): Promise<ConvertApiResult>ConvertApiOptions:
interface ConvertApiOptions {
format: ImageFormat; // required — "jpeg"|"png"|"webp"|"avif"|"gif"|"tiff"
quality?: number; // default 85
width?: number;
height?: number;
removeMetadata?: boolean; // default false
maintainAspectRatio?: boolean; // default true
allowUpscaling?: boolean; // default false (prevents enlargement)
crop?: {
left: number;
top: number;
width: number;
height: number;
};
rotate?: number; // arbitrary degrees
autoRotate?: boolean; // apply and strip EXIF orientation tag
flip?: boolean; // horizontal mirror
flop?: boolean; // vertical mirror
background?: string; // CSS color string
grayscale?: boolean;
blur?: number; // Gaussian sigma 0.3–1000
sharpen?: boolean;
normalize?: boolean;
trim?: boolean;
}ConvertApiResult:
interface ConvertApiResult {
buffer: Buffer;
info: {
inputBytes: number;
outputBytes: number;
width: number;
height: number;
format: string;
};
}Examples:
import { convert } from '@dutchbase/img-convert'
import fs from 'fs/promises'
// Convert a local file
const result = await convert('./photo.jpg', {
format: 'webp',
quality: 85,
width: 1280,
})
await fs.writeFile('./photo.webp', result.buffer)
console.log(`${result.info.inputBytes} → ${result.info.outputBytes} bytes`)
// Convert from a URL
const fromUrl = await convert('https://example.com/image.png', {
format: 'avif',
quality: 70,
})
// Convert from an in-memory Buffer (e.g. from a multipart upload handler)
const fromBuffer = await convert(req.file.buffer, {
format: 'jpeg',
quality: 90,
background: '#ffffff', // flatten PNG transparency before JPEG encoding
})
// Crop then resize
const cropped = await convert('./screenshot.png', {
format: 'webp',
crop: { left: 100, top: 50, width: 800, height: 600 },
width: 400,
})
// Strip EXIF, rotate to EXIF orientation, then re-encode
const clean = await convert('./camera.jpg', {
format: 'jpeg',
autoRotate: true,
removeMetadata: true,
quality: 88,
})getInfo()
function getInfo(input: string | Buffer): Promise<ImageInfo>interface ImageInfo {
format: string;
width: number;
height: number;
filesize: number;
hasAlpha: boolean;
hasExif: boolean;
colorSpace: string;
isAnimated: boolean;
channels?: number;
density?: number;
}Examples:
import { getInfo, convert } from '@dutchbase/img-convert'
const info = await getInfo('./photo.jpg')
// { format: 'jpeg', width: 4032, height: 3024, filesize: 3891200,
// hasAlpha: false, hasExif: true, colorSpace: 'srgb', isAnimated: false }
// Conditional conversion: don't flatten alpha if not needed
const { hasAlpha } = await getInfo('./image.png')
const result = await convert('./image.png', {
format: 'jpeg',
...(hasAlpha ? { background: '#ffffff' } : {}),
})
// Skip animated GIFs in a batch
const infos = await Promise.all(paths.map(p => getInfo(p)))
const staticOnly = paths.filter((_, i) => !infos[i].isAnimated)batch()
function batch(
items: BatchApiItem[],
options?: BatchApiOptions
): Promise<BatchApiResult[]>interface BatchApiItem {
input: string; // file path or URL
output?: string; // output file path — auto-derived if omitted
format: ImageFormat;
quality?: number;
width?: number;
height?: number;
removeMetadata?: boolean;
}
interface BatchApiOptions {
concurrency?: number; // default 4
outputDir?: string; // write all outputs here when output not specified per-item
}
interface BatchApiResult {
input: string;
output: string;
inputBytes: number;
outputBytes: number;
width: number;
height: number;
format: string;
quality: number;
}Example:
import { batch } from '@dutchbase/img-convert'
const results = await batch(
[
{ input: './src/hero.png', format: 'webp', quality: 90 },
{ input: './src/thumb.jpg', format: 'avif', width: 200 },
{ input: './src/banner.gif', format: 'webp' },
],
{ concurrency: 4 }
)
for (const r of results) {
const pct = ((1 - r.outputBytes / r.inputBytes) * 100).toFixed(1)
console.log(`${r.input} → ${r.output} (${pct}% smaller)`)
}
// ./src/hero.png → ./src/hero.webp (67.3% smaller)
// ./src/thumb.jpg → ./src/thumb.avif (71.0% smaller)
// ./src/banner.gif → ./src/banner.webp (44.2% smaller)REST API
The web application exposes a single endpoint. It can be called directly from any HTTP client.
POST /api/convert
Accepts multipart/form-data. Returns the converted image as binary.
Request fields:
Field | Type | Required | Notes |
| File | Yes | Source image. Max 50 MB. |
| string | Yes |
|
| string | No | Integer 1–100, default |
| string | No | Target width in pixels |
| string | No | Target height in pixels |
|
| No | Default |
|
| No | Strip EXIF, default |
|
| No | Allow enlargement, default |
Success response:
Status:
200Body: raw image bytes
Headers:
Content-Type— format MIME typeContent-Disposition: attachment; filename="<name>.<ext>"X-Output-Size— output size in bytes (string)X-Output-Filename— sanitized output filename
Error response shape:
interface ApiErrorResponse {
error: string; // machine-readable error code
message: string; // human-readable description
field?: string; // which form field caused the error, if applicable
}Error codes:
HTTP status | Error code | Cause |
|
| No file in request |
|
|
|
|
| Requested output format is input-only |
|
| Quality is not an integer in 1–100 |
|
| Width or height is not a positive integer |
|
| File exceeds 50 MB |
|
| Magic-byte check failed (declared MIME ≠ actual content) |
|
| Pixel dimensions exceed 25 megapixels |
|
| HEIC live photo detected |
|
| Unhandled Sharp error |
curl example:
curl -s -X POST http://localhost:3000/api/convert \
-F "file=@photo.jpg" \
-F "targetFormat=webp" \
-F "quality=85" \
-o output.webp
# Check output size from response header
curl -sI -X POST http://localhost:3000/api/convert \
-F "file=@photo.jpg" \
-F "targetFormat=webp" \
| grep X-Output-SizeFormat Support
Input formats
Format | MIME type(s) | Notes |
JPEG |
| |
PNG |
| Transparency supported |
WebP |
| Animated WebP supported |
AVIF |
| |
GIF |
| Animated GIF supported |
TIFF |
| |
HEIC / HEIF |
| Pre-decoded via |
SVG |
| Rasterized via librsvg (Sharp built-in). Output size = SVG declared dimensions unless overridden with |
Output formats
Format | Quality flag | Typical use |
| Yes | Photos, no transparency requirement |
| Compression derived | Lossless, transparency, screenshots |
| Yes | Web images — best size/quality trade-off for most content |
| Yes | Smallest files, highest quality per byte. Slower encoding. |
| No | Animated images |
| Yes | Print workflows, archival storage |
Format conversion notes
Transparency → JPEG. JPEG has no alpha channel. Without --background, transparent pixels become black. Always pass --background "#ffffff" (or your target fill color) when converting PNG/WebP/AVIF with transparency to JPEG.
Animated GIF to static format. Converting an animated GIF to JPEG or PNG captures only the first frame. To preserve animation, convert to WebP (which supports animation).
SVG rasterization. Sharp uses librsvg to rasterize SVGs. The default raster size is the SVG's declared width/height attributes. Pass --width or --height to control the output pixel dimensions.
HEIC decoding. Apple's HEIC format cannot be decoded by Sharp directly. img-convert uses the heic-convert library to decode HEIC to a PNG buffer first, then passes it to Sharp. This adds latency and is single-threaded per file.
PNG quality. PNG is lossless, so --quality controls Sharp's compressionLevel (derived as Math.round((100 - quality) / 11)). Higher quality = lower compression = faster encoding + larger files. The image data is identical either way.
Processing Options
The pipeline runs in this fixed order. Each step is opt-in and independent.
Input
→ HEIC pre-decode (if source is HEIC)
→ Decompression bomb guard (rejects > 25 megapixels)
→ Metadata handling (strip or preserve)
→ Auto-rotate / Rotate
→ Flip / Flop
→ Crop
→ Resize
→ Grayscale
→ Normalize
→ Blur
→ Sharpen
→ Trim
→ Background flatten (before JPEG encoding)
→ Format encode
→ OutputOption | CLI | API field | Notes |
Quality |
|
| 1–100. Applies to JPEG, WebP, AVIF, TIFF. |
Resize |
|
| Fits within dimensions. No upscaling unless |
Metadata |
|
| Strips EXIF/XMP/IPTC. ICC profile always kept. |
Crop | — |
| Runs before resize. Pixel coordinates in original image space. |
Auto-rotate | — |
| Applies EXIF orientation and strips the tag. |
Rotate |
|
| Any angle. Empty corners filled with |
Flip |
|
| Left–right mirror. |
Flop |
|
| Top–bottom mirror. |
Background |
|
| CSS color string. Used for rotation corners and JPEG flattening. |
Grayscale |
|
| Desaturates to single luminance channel. |
Blur |
|
| Gaussian blur, sigma 0.3–1000. |
Sharpen |
|
| Unsharp mask with Sharp defaults. |
Normalize |
|
| Stretches histogram to full range. |
Trim |
|
| Removes uniform-color edge pixels. |
Architecture
img-convert/
├── cli/
│ ├── index.ts # Commander CLI — convert, info, batch, mcp subcommands
│ ├── helpers.ts # Pure functions: path building, format detection, option mapping
│ └── mcp.ts # MCP server — registers tools, handles stdio transport
├── lib/
│ ├── imageProcessor.ts # Core Sharp pipeline — single source of truth for all interfaces
│ ├── api.ts # Programmatic Node.js API: convert(), getInfo(), batch()
│ ├── heicDecoder.ts # HEIC → PNG buffer pre-decode step
│ └── processingQueue.ts # Concurrency semaphore for the REST endpoint
├── types/
│ ├── index.ts # Shared types: ImageFormat, ConvertOptions, API types
│ └── client.ts # Browser-safe re-export + MIME → ImageFormat detection helper
├── app/
│ ├── api/convert/
│ │ └── route.ts # Next.js Route Handler: POST /api/convert
│ ├── layout.tsx
│ └── page.tsx
├── components/
│ ├── ImageConverter.tsx # Top-level stateful client component
│ ├── DropZone.tsx # Drag-and-drop file input
│ ├── ConvertOptions.tsx # Format selector, quality slider, resize controls
│ ├── ConvertResult.tsx # Download link + size comparison
│ ├── BatchQueue.tsx # Multi-file batch UI with per-item status
│ └── ImagePreview.tsx # Source image preview
├── dist/
│ └── cli/ # Compiled CLI output (CommonJS, aliases resolved by tsc-alias)
└── __tests__/
├── imageProcessor.test.ts
├── cli.test.ts
├── route.test.ts
├── batchQueue.test.ts
└── ...Single pipeline, four interfaces
The processImage() function in lib/imageProcessor.ts is the canonical Sharp pipeline. It is called by:
CLI (
cli/index.ts) — reads files or stdin, writes to diskNode.js API (
lib/api.ts) — wraps processImage with input resolution and structured result objectsREST API (
app/api/convert/route.ts) — validates multipart form fields and returns binary HTTP responseMCP server (
cli/mcp.ts) — translates tool call arguments into processImage options, writes files, returns JSON
All four interfaces produce identical output for identical inputs. There is no separate code path for any interface.
Concurrency model
Interface | Mechanism | Default limit |
CLI |
|
|
Node.js API |
|
|
REST API |
|
|
MCP batch |
|
|
The REST endpoint's semaphore is intentionally conservative (single slot) to prevent memory exhaustion under concurrent browser requests. CLI and API concurrency is user-controlled.
Build system
Config | Purpose |
| Next.js app — |
| CLI + API — |
| Post-processes compiled JS to rewrite |
The two tsconfig approach is intentional: the Next.js bundler handles module resolution differently from Node.js require(). Sharing one config would require compromises in both directions.
Development
Setup
git clone https://github.com/dutchbase/img-convert
cd img-convert
npm installCommands
npm run dev # Start Next.js dev server at http://localhost:3000
npm run build # Production Next.js build + type-check
npm run build:cli # Compile CLI + API to dist/cli/ (required before running img-convert locally)
npm run lint # ESLint
npm test # Jest unit tests
npm run test:coverage # Jest with coverage report
npm run test:e2e # Playwright end-to-end tests
npm run test:all # Unit + E2EAdding a new output format
Add the format key to the
ImageFormatunion intypes/index.tsAdd entries to
FORMAT_LABELS,FORMAT_MIME,FORMAT_EXTENSIONSAdd the format to
OUTPUT_FORMATS(orINPUT_ONLY_FORMATSif Sharp cannot encode it)Add a case to
applyFormat()inlib/imageProcessor.tsAdd the MIME type to
detectFormat()inlib/imageProcessor.tsAdd the MIME type to
detectFormatFromMime()intypes/client.tsAdd the extension to
EXT_TO_FORMATincli/helpers.tsAdd the MIME type to the
acceptattribute incomponents/DropZone.tsx
Adding a new processing option
Add the field to
ConvertOptionsintypes/index.tsAdd to
ConvertApiOptionsintypes/index.tsif it should be part of the public APIApply in
lib/imageProcessor.tsin the correct pipeline positionAdd the CLI flag to
programincli/index.tsWire through
buildConvertOptions()incli/helpers.tsExpose in the MCP
convert_imagetool input schema incli/mcp.tsAdd a UI control in
components/ConvertOptions.tsxif it should be in the web UI
Test structure
Tests live in __tests__/ and run with Jest + ts-jest. The test environment is configured per-file in jest.config.ts:
Node environment:
imageProcessor.test.ts,route.test.ts,cli.test.ts,heicDecoder.test.ts,animatedGif.test.tsJSDOM environment:
imageConverter.test.tsx,dropZone.test.ts,batchQueue.test.ts,processingQueue.test.ts
Sharp operations use the actual Sharp library in tests (no mocking) with small fixture images in __tests__/fixtures/.
Security considerations
The REST endpoint applies multiple defense layers:
File size limit — 50 MB hard cap before reading body
MIME allowlist — source format must be a recognized image type
Magic-byte verification —
file-typechecks actual file contents, not just the browser-supplied MIME headerPixel dimension check — rejects images exceeding 25 megapixels before allocating decode buffers
Sharp decompression limit —
limitInputPixels: 25_000_000passed to every Sharp constructorFilename sanitization —
Content-Dispositionfilename is stripped of all characters except[a-zA-Z0-9._-]
CI/CD
.github/workflows/ci.yml runs on every push and PR:
Tests on Node 18, 20, and 22
Runs
npm test,npm run build,npm run build:cliVerifies the compiled CLI binary executes without error
.github/workflows/release.yml triggers on v* tags:
Runs full test suite
Builds CLI
Publishes to npm with provenance attestation
# Publish a new release
npm version patch # or minor / major
git push --follow-tags
# GitHub Actions handles the restContributing
Pull requests are welcome.
Open an issue first for non-trivial changes.
Keep
processImage()as the single pipeline — don't fork processing logic between CLI, API, REST, and MCP.Maintain the stderr/stdout contract: data on stdout, progress on stderr.
--jsonshould always produce parseable output.Add tests for new features and bug fixes. The test suite should remain green with
npm test.Run
npm test && npm run build:clibefore submitting.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Image & PDF tools for AI agents: compress, convert, resize, PDF, AI vision, pipeline.
Video, audio, and image processing for AI agents: convert, transcribe, upscale - 150+ operations.
Image processing for AI agents: resize, convert, compress, crop, and web-ready AI-generated images.
Image toolkit: resize, convert, compress, crop, metadata, hashes, favicons, OCR, QR codes, barcodes.
181
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to perform image processing tasks such as sprite sheet splitting, resizing, cropping, and batch operations on local images.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to perform GIMP-style image operations such as open, resize, crop, flip, rotate, blur, desaturate, text overlay, export, and batch processing via MCP tools, supporting both mock (Pillow) and live GIMP backends.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to process images locally via file paths—converting, resizing, removing backgrounds, smart cropping, upscaling, reading or stripping metadata, and batch processing—without files ever leaving the device.MIT
- AlicenseAqualityBmaintenanceEnables AI agents to perform local image operations such as circle cropping, cropping, resizing, compressing, converting, and inspecting images without uploading files or making network calls.787 npmMIT