optiq-mcp
optiq-mcp lets AI agents analyze and optimize project images locally through two MCP tools backed by Optiq's compression engine.
Analyze images without writing anything using
analyze_images: a read-only dry run that reports the chosen format, before/after bytes, savings, quality (SSIM or exact), warnings, errors, and totals.Optimize images and write results using
optimize_images: either overwrite files in place while keeping format/name, or write to an output directory while allowing format changes.Process files and folders recursively, including jpg, jpeg, png, webp, avif, and gif; dot-folders are skipped.
Choose quality modes:
lossless,visually-lossless,balanced, orfast, with optional customminQuality.Control output behavior: allowed
formats,keepFormat,maxSizedownscaling,minSaving,stripMetadata, andreoptimize.Get per-file and aggregate results, including file paths, sizes, saved bytes, quality, warnings, errors, omitted-file count, and elapsed seconds.
Safely repeat optimization: already-compressed files are skipped by default, and in-place writes use atomic rename.
Try the demo · Documentation · Benchmarks
Browser-side image optimisation for uploads. For every image Optiq encodes a bounded set of candidates (MozJPEG, libwebp, libavif, oxipng — compiled to WebAssembly), measures each one, and returns the smallest output that satisfies your constraint:
lossless— decoded pixels (including alpha) are verified identical to the input;visually-lossless— pixels may differ; SSIM ≥ 0.99 against the input (configurable);balanced(default) — SSIM ≥ 0.975, smaller search budget, faster AVIF preset.
AVIF is held to a stricter SSIM threshold than JPEG and WebP. It was calibrated against the independent SSIMULACRA2 metric, which showed SSIM is too lenient on AVIF's smoothing.
The same engine also runs in Node and powers:
a CLI (
npx optiq-image);a Vite plugin;
an MCP server for AI agents (
optiq-mcp, source inmcp/).
In the browser, work runs in a Web Worker; the main thread only posts a Blob and receives an ArrayBuffer. Results carry the
chosen codec/settings, every candidate tried, the measured quality, warnings, fallbacks and unmet constraints.
Benchmarks: see Benchmark results and
bench/results/RESULTS.md. Optiq is not uniformly better than the alternatives; the trade-offs (size vs. time vs. memory) are listed there per category.
Install
pnpm add optiq-image # or npm / yarnRequirements: an ES2022 browser with module Workers and WebAssembly (Chrome/Edge 80+, Firefox 114+, Safari 15+).
AVIF output is only useful where your audience can decode AVIF (Chrome 85+, Firefox 93+, Safari 16.4+); remove it
with autoFormats: ["jpeg", "png", "webp"] otherwise.
Bundler setup (Vite 7 and 8)
// vite.config.ts
export default defineConfig({
worker: { format: "es" }, // codecs are loaded with dynamic import() inside the worker
optimizeDeps: { // dev only: pre-bundling breaks `new URL("x.wasm", import.meta.url)`
exclude: ["optiq-image", "@jsquash/jpeg", "@jsquash/png", "@jsquash/webp", "@jsquash/avif", "@jsquash/oxipng", "@jsquash/resize"],
},
});webpack 5 and Parcel 2 understand new Worker(new URL("./worker.js", import.meta.url), { type: "module" }) and
new URL("*.wasm", import.meta.url) natively. For anything else pass workerFactory to createCompressor.
Each codec is a separate chunk, fetched on first use (AVIF ≈ 3.5 MB, others 35–350 KB).
Related MCP server: 🪄 ImageSorcery MCP
Usage
import { compressImage, isOptiqError } from "optiq-image";
const controller = new AbortController();
try {
const result = await compressImage(file, {
mode: "balanced",
maxWidthOrHeight: 2560,
signal: controller.signal,
onProgress: (e) => console.log(e.stage, Math.round(e.progress * 100), e.bestBytes),
});
result.blob; // Blob with the right MIME type (image/avif, image/webp, ...)
result.fileName; // "IMG_0042.webp" — extension matches the encoded bytes
result.sizeReductionRatio, result.qualityMetric, result.codec, result.warnings;
if (!result.constraintsMet) console.warn(result.unmetConstraints);
} catch (e) {
if (isOptiqError(e)) console.warn(e.code, e.message); // e.g. UNSUPPORTED_FORMAT, ABORTED
// the original `file` is untouched — upload it instead
}React:
function useCompressed(file?: File) {
const [state, set] = useState<{ result?: CompressionResult; error?: unknown; progress: number }>({ progress: 0 });
useEffect(() => {
if (!file) return;
const ac = new AbortController();
compressImage(file, { signal: ac.signal, onProgress: (e) => set((s) => ({ ...s, progress: e.progress })) })
.then((result) => set({ result, progress: 1 }), (error) => !ac.signal.aborted && set({ error, progress: 0 }));
return () => ac.abort(); // terminates the worker job
}, [file]);
return state;
}For explicit control over workers use createCompressor({ maxWorkers, workerFactory, codecFallback, allowMainThread, recycleAfterPixels }).
Options
Option | Default | Meaning |
|
|
|
| 0.99 / 0.975 | SSIM threshold (ignored in lossless). Gradients use a stricter value half-way to 1 |
| – | Upper bound. If impossible without violating quality, the result lists it in |
| – | Downscale longest side. In |
|
|
|
| all four | Formats |
|
|
|
| 10 / 16 / 4 | Encode budget (balanced / visually-lossless / lossless) |
|
|
|
|
| Keep EXIF/XMP/ICC. |
|
| Animated inputs are only optimised at container level. |
|
| Keep the original unless re-encoding saves at least this |
| 100 MiB / 50 MP | Checked before reading / before decoding |
|
|
|
Progress stages: reading → probing → decoding → (resizing) → analyzing → encoding … → finalizing → done.
Error codes: INVALID_INPUT, UNSUPPORTED_FORMAT, MALFORMED_INPUT, INPUT_TOO_LARGE, INVALID_OPTIONS,
INCOMPATIBLE_OPTIONS (e.g. JPEG requested for a transparent image), CONSTRAINTS_UNSATISFIABLE,
CODEC_UNAVAILABLE, UNSUPPORTED_ENVIRONMENT, WORKER_ERROR, ABORTED (name === "AbortError").
React, CLI, Vite plugin
import { useOptiq } from "optiq-image/react";
const { status, result, progress, cancel } = useOptiq(file, { mode: "fast" });View example (upload form with progress, cancel and S3) · React hook docs
npx optiq-image ./photos --out ./photos-optimized # static images, in parallel worker threadsimport { optiq } from "optiq-image/vite"; // optimises build output in place
export default defineConfig({ plugins: [optiq()] });MCP server (optiq-mcp)
This repository contains an MCP server, published on npm as
optiq-mcp and in the official MCP Registry as
io.github.Baukaalm/optiq-mcp. Its source is in mcp/:
mcp/src/index.tsis the implementation;mcp/test/holds end-to-end tests that run it with the official MCP client;mcp/server.jsonis the registry entry.
It is a local stdio server built on the TypeScript MCP SDK, and it gives AI agents two tools for the images in a project:
Tool | Writes | What it does |
| no ( | Dry run. Per image it reports the format Optiq would choose, the bytes before and after, and the measured quality |
| yes ( |
|
claude mcp add optiq -- npx -y optiq-mcp # Claude Code{ "mcpServers": { "optiq": { "command": "npx", "args": ["-y", "optiq-mcp"] } } }The JSON config works for Claude Desktop, Cursor and other MCP clients.
For agents that drive the CLI instead, there is an Agent Skill. The documentation site also has an llms.txt. See the agents guide.
Uploading to S3
import { compressAndUpload } from "optiq-image/upload";
const out = await compressAndUpload(file, { requestPresignedUpload: (req) => fetch("/api/presign", …).then((r) => r.json()) });Compression is optional and never blocks the upload: if it fails or does not help, the original is uploaded. Presigned URLs must come from your backend — see docs/guide/s3.md for the contract and a backend sketch.
Supported formats
Input | Output | |
JPEG | 8-bit RGB/gray (CMYK, 12-bit: kept as is) | MozJPEG, progressive |
PNG | all incl. 16-bit, APNG (container-level only) | oxipng (lossless) |
WebP | lossy, lossless, animated (container-level only) | lossy / lossless |
AVIF | still (animated / rotated: kept as is) | libavif |
GIF | static (converted), animated (kept as is) | – |
HEIC/HEIF | detected → | – |
SVG | detected → returned unchanged, never rasterised | – |
Formats are detected from file content. Extensions and MIME types are ignored (a .jpg that is a PNG is a PNG).
Guarantees and limitations
Lossless means decoded RGBA equality, verified per candidate, for the decoder Optiq uses. JPEG decoders are not bit-exact across implementations, so “lossless” for a JPEG input is only ever a byte-level container rewrite (metadata removal), never a re-encode.
Transparency is never dropped: JPEG is excluded when any pixel has alpha < 255.
Orientation: EXIF orientation is applied to the pixels of re-encoded outputs (tag reset to 1) and preserved in container-level outputs.
Colour: ICC profiles are carried into JPEG/PNG/WebP outputs. AVIF output is tagged sRGB and is not used for images with a non-sRGB profile. sRGB profiles are dropped only with
preserveMetadata: false(or for AVIF output, where thenclxsRGB tag is equivalent). No colour conversion is performed.Metadata: kept by default. AVIF output is skipped when EXIF/XMP must be preserved (no AVIF metadata writer). With
preserveMetadata: false, EXIF (including GPS), XMP, comments and trailing data are removed.Animation is never silently flattened.
SSIM is a proxy for perception, not perception: it under-weights banding in smooth gradients and chroma bleeding. Validate thresholds on your own content.
Memory: large images need several hundred MB inside the worker (see benchmarks). WASM memory is returned by terminating the worker, which happens automatically after jobs above 8 MP.
Speed: searching costs multiple encodes. Use
balanced, lowermaxCandidates,searchStrategy: "fixed", or drop AVIF fromautoFormatswhen latency matters more than bytes.
Licensing
Optiq: Apache-2.0. Bundled codecs (via jSquash, Apache-2.0 wrappers): MozJPEG (IJG + BSD-3 + zlib),
libwebp (BSD-3), libavif (BSD-2) with libaom (BSD-2 + AOMedia patent licence), Rust png, oxipng, resize
(MIT/Apache-2.0). All permit commercial redistribution with notices. No GPL code is included
(libimagequant was rejected for that reason).
Benchmark results
The corpus has 24 images (listed in bench/corpus/manifest.json):
10 Kodak photos (8 JPEG q92, 2 PNG);
3 phone-style 12 MP JPEGs;
2 screenshots and 2 text graphics;
4 transparent illustrations;
3 gradients.
Measured on an Apple M4 Pro (14 cores, 24 GB), macOS arm64, Node 24.15 and headless Chromium 156. Every output was decoded with the same decoders and scored against the input twice:
with SSIM, the metric Optiq uses to choose outputs, so it favours Optiq;
independently with SSIMULACRA2 (libjxl's
ssimulacra2), a perception-tuned metric that Optiq never sees.
On the SSIMULACRA2 scale, 90 is very high (indistinguishable at 1:1), 80 is high, 70 is medium and 50 is low. Full
tables and per-image rows are in bench/results/RESULTS.md.
Perceptual, all 24 images (Node; same encoders as the browser)
Tool | Total output | Reduction | SSIMULACRA2 median | SSIMULACRA2 ≥ 80 | Median time | Median peak RSS |
Optiq | 13.25 → 5.29 MB | 60.1% | 79.4 | 10/24 | 1.6 s | 301 MB |
Optiq | 13.25 → 5.49 MB | 58.5% | 78.0 | 9/24 | 410 ms | 175 MB |
Optiq | 13.25 → 5.12 MB | 61.4% | 79.0 | 10/24 | 1.6 s | 301 MB |
Optiq | 13.25 → 6.70 MB | 49.5% | 85.8 | 19/24 | 2.4 s | 322 MB |
libvips WebP q80 | 13.25 → 6.50 MB | 51.0% | 78.6 | 6/24 | 59 ms | 105 MB |
libvips MozJPEG q80 (opaque only) | 12.82 → 6.87 MB | 46.4% | 77.8 | 6/20 | 54 ms | 106 MB |
libvips AVIF q50 | 13.25 → 3.41 MB | 74.3% | 71.2 | 11/24 | 590 ms | 151 MB |
Browser (Chromium), against browser-image-compression 2.0.2 and canvas
Tool | Total output | Reduction | SSIMULACRA2 median | SSIMULACRA2 ≥ 80 | Median time |
Optiq | 13.25 → 5.29 MB | 60.1% | 79.4 | 10/24 | 1.3 s |
Optiq | 13.25 → 5.49 MB | 58.5% | 78.0 | 9/24 | 331 ms |
Optiq | 13.25 → 6.70 MB | 49.5% | 85.8 | 19/24 | 2.2 s |
browser-image-compression | 13.25 → 10.21 MB | 22.9% | 91.9 | 17/24 | 78 ms |
browser-image-compression | 13.25 → 6.52 MB | 50.8% | 78.5 | 6/24 | 37 ms |
browser-image-compression | 13.25 → 7.14 MB | 46.1% | 92.4 | 21/21 (3 large photos were downscaled, not scored) | 128 ms |
canvas WebP 0.8 | 13.25 → 6.52 MB | 50.8% | 78.5 | 6/24 | 36 ms |
What the numbers say:
balancedvs WebP 0.8. Files are 19% smaller with about the same SSIMULACRA2 (median 79.4 vs 78.5–78.6). Per image,balancedis smaller and scores higher on 11/24, and is never worse on both. The other 13 are trade-offs.visually-losslessvs WebP 0.8. About the same total size (6.70 vs 6.50 MB) at clearly higher quality: median 85.8 vs 78.6, and 19/24 images at 80+ vs 6/24.AVIF q50 is much smaller, at lower quality (median 71.2). Whether that trade is right is a product decision. Optiq spends bytes to stay above its threshold.
An earlier version claimed more than the data supports. It said "24/24 meet SSIM 0.975 vs 18/24 for WebP 0.8". SSIMULACRA2 showed that SSIM was too lenient on AVIF:
balancedthen had a median of 73.9, below WebP 0.8. AVIF is now held to a stricter SSIM threshold, calibrated against SSIMULACRA2 on 91 held-out images (modes). That costs +2.7% bytes inbalanced.Gradients remain the weak spot. They score 64–72 in every Optiq mode and for every codec, because SSIM under-rates banding.
Category matters.
Screenshots: −89.7% vs −62.8% for WebP q80.
Text: −91.8% vs −46.6% (lossless WebP and screen-content encodes win there).
Large 12 MP phone photos: −41.5% with metadata kept, −43.8% without, vs −30.7% for WebP q80.
Optiq is slow and memory-hungry by comparison. It runs several encodes plus decodes and quality checks per image.
Typical: 1–4 s per image, 3–5 s for 12 MP photos.
Worst case, a 5 MP dashboard screenshot: 17 s in
balanced, 48 s invisually-lossless.Memory: median peak RSS ≈ 300 MB, up to 1.1 GB.
Single canvas/libvips encodes take 5–800 ms.
Work runs in a worker, so the UI stays responsive (max main-thread gap 11 ms during a 12 MP job).
Lossless (sc-lossless, Node): pixel-exact on 24/24 and never larger than the input (total −20.4%).
PNG photos −27% (oxipng), screenshots −76% and text −91% (about the same as libvips lossless WebP effort 6),
transparent illustrations −42% and gradients −66%, where libvips PNG max compression reached −60% / −76% but its
outputs were not pixel-exact against the reference. JPEG inputs: only metadata can be removed losslessly (0% with
the default preserveMetadata: true; no jpegtran in this build).
Development
Toolchain: Node ≥ 20 (tested with 24.15), pnpm 11, Playwright Chromium (pnpm exec playwright install chromium chromium-headless-shell).
No Rust toolchain is required: all WASM comes prebuilt from pinned jSquash packages.
pnpm install
pnpm typecheck
pnpm test # unit + integration tests in Node (real WASM codecs)
pnpm test:browser # builds, bundles a consumer page with Vite, runs it in Chromium (incl. CSP/no-WASM fallback)
pnpm test:demo # demo end-to-end (light/dark/mobile): compress → presign → PUT to mock S3
pnpm pack:check # pack the tarball, install it in a fresh Vite app, typecheck, build, run (does not publish)
pnpm demo # React + HeroUI demo with a mock presigned-S3 backend
pnpm docs:dev # VitePress documentation site
pnpm bench:corpus # download/render the benchmark corpus (24 images)
pnpm bench:node # Optiq vs libvips (sharp), with peak RSS per run
pnpm bench:browser # Optiq vs browser-image-compression and canvas, in Chromium
pnpm bench:report # writes bench/results/RESULTS.mdDocumentation site (guides, API reference generated from the TSDoc comments, benchmarks, architecture):
pnpm docs:dev, or read the Markdown in docs/. Design notes: docs/architecture.md.
Available Tools
2 toolsanalyze_imagesAnalyze images (dry run)ARead-only
Read-only dry run: runs the full optimisation in memory and reports, per image, the format Optiq would choose, bytes before and after, and the measured quality (SSIM, or pixel-exact). Writes nothing. Use it first to show the user the expected savings, then call optimize_images with the same arguments. Main parameters: paths (files or folders), mode (how strict the quality check is), keepFormat/formats (whether and to what the format may change), maxSize (downscale), stripMetadata (drop EXIF/GPS). Cost: it does the same work as optimize_images, several encodes per image, typically 0.3-3 s per image on a modern laptop (fast mode is quicker; very large screenshots in visually-lossless can take tens of seconds). For hundreds of images, try one subfolder first. Progress is reported per file and the call can be cancelled.
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | How strict the quality check is. lossless: decoded pixels verified identical (JPEG inputs only get metadata changes). visually-lossless: SSIM >= 0.99. balanced (default): SSIM >= 0.975. fast: same threshold as balanced, no AVIF, at most 5 encodes, about 3x faster. Use visually-lossless for hero images, artwork and product photos. | balanced |
| paths | Yes | Image files and/or folders. Folders are searched recursively for jpg, jpeg, png, webp, avif and gif; dot-folders are skipped. Use absolute paths; relative ones resolve against the server's working directory. | |
| formats | No | Formats Optiq may choose when the format may change. Default: all four (fast mode: no avif). Drop avif if the site must support browsers older than Safari 16.4. JPEG is never chosen for images with transparency. | |
| maxSize | No | Downscale so the longest side is at most this many pixels, e.g. 2560 for camera photos on a website. Smaller images are not enlarged. Allowed in lossless mode too (the resize itself changes pixels). | |
| minSaving | No | Keep a file unchanged unless it gets at least this share smaller (0.05 = 5%). Default 0.02. Raise it to avoid churning files for tiny gains. | |
| keepFormat | No | Never change the file format (a .png stays PNG). Default: true for in-place writes and for analysis, false when writing to outDir. Set false in analysis to see what converting to WebP/AVIF would save. | |
| minQuality | No | Custom SSIM threshold for perceptual modes instead of the mode's default, e.g. 0.98. Higher keeps more detail and saves less. Ignored in lossless mode. | |
| stripMetadata | No | Remove EXIF (including GPS location, camera serial) and XMP. Recommended before publishing personal photos. Colour profiles that matter (non-sRGB ICC) and image orientation are preserved either way. |
Output Schema
| Name | Required | Description |
|---|---|---|
| files | Yes | |
| dryRun | Yes | |
| totals | Yes | |
| omittedFiles | Yes | Files processed but not listed (the list is capped); totals include them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, but the description adds substantial behavioral context beyond them: it writes nothing, performs the same work as optimize_images including several encodes per image, gives expected timing ranges, notes progress reporting and cancellation. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but front-loaded: read-only purpose and output are stated first, followed by the optimize_images relationship, parameter summary, cost profile, and scaling advice. Every sentence contributes actionable information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Although an output schema exists, the description still conveys the essential return shape and operational behavior an agent needs: per-image format, bytes, and quality metrics, plus performance expectations, cancellation, and sibling-tool relationship. It is complete for this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already fully documents all eight parameters. The description summarizes several main parameters but mostly repeats schema content and omits minSaving and minQuality, adding only marginal semantic value beyond the structured fields.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: a read-only dry run that runs image optimisation in memory and reports per-image chosen format, before/after bytes, and measured quality. It clearly distinguishes itself from optimize_images by describing the analysis-only behavior and the sequencing relationship.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly instructs to use this first to show expected savings, then call optimize_images with the same arguments. It also gives practical usage context: try one subfolder first for hundreds of images, and the call can be cancelled.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
optimize_imagesOptimize imagesADestructiveIdempotent
Optimises images and writes the results. If the user only wants to know how much could be saved, or has not agreed to changes yet, use analyze_images instead: it takes the same arguments and writes nothing. write: "in-place" overwrites each file and keeps its format and name, so references in code stay valid (atomic rename). write: "out-dir" writes to outDir, mirroring the folder structure, and may change formats; extensions change accordingly, so update references. A file is only replaced when the result passes the quality check and is smaller. Files Optiq already compressed lossily are skipped (unless reoptimize is true), so calling this twice is safe. Choosing parameters: mode visually-lossless for hero images, artwork and product photos, lossless for logos and UI assets that must stay exact, balanced otherwise; stripMetadata before publishing personal photos (GPS); formats without avif if old browsers matter. Takes the same time as analyze_images (typically 0.3-3 s per image).
| Name | Required | Description | Default |
|---|---|---|---|
| mode | No | How strict the quality check is. lossless: decoded pixels verified identical (JPEG inputs only get metadata changes). visually-lossless: SSIM >= 0.99. balanced (default): SSIM >= 0.975. fast: same threshold as balanced, no AVIF, at most 5 encodes, about 3x faster. Use visually-lossless for hero images, artwork and product photos. | balanced |
| paths | Yes | Image files and/or folders. Folders are searched recursively for jpg, jpeg, png, webp, avif and gif; dot-folders are skipped. Use absolute paths; relative ones resolve against the server's working directory. | |
| write | Yes | "in-place" overwrites inputs; "out-dir" writes to outDir. | |
| outDir | No | Required when write is "out-dir". | |
| formats | No | Formats Optiq may choose when the format may change. Default: all four (fast mode: no avif). Drop avif if the site must support browsers older than Safari 16.4. JPEG is never chosen for images with transparency. | |
| maxSize | No | Downscale so the longest side is at most this many pixels, e.g. 2560 for camera photos on a website. Smaller images are not enlarged. Allowed in lossless mode too (the resize itself changes pixels). | |
| minSaving | No | Keep a file unchanged unless it gets at least this share smaller (0.05 = 5%). Default 0.02. Raise it to avoid churning files for tiny gains. | |
| keepFormat | No | Never change the file format (a .png stays PNG). Default: true for in-place writes and for analysis, false when writing to outDir. Set false in analysis to see what converting to WebP/AVIF would save. | |
| minQuality | No | Custom SSIM threshold for perceptual modes instead of the mode's default, e.g. 0.98. Higher keeps more detail and saves less. Ignored in lossless mode. | |
| reoptimize | No | Re-encode files Optiq already compressed lossily. Each lossy pass loses a little more quality; leave false unless the user asks. | |
| stripMetadata | No | Remove EXIF (including GPS location, camera serial) and XMP. Recommended before publishing personal photos. Colour profiles that matter (non-sRGB ICC) and image orientation are preserved either way. |
Output Schema
| Name | Required | Description |
|---|---|---|
| files | Yes | |
| dryRun | Yes | |
| totals | Yes | |
| omittedFiles | Yes | Files processed but not listed (the list is capped); totals include them. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations flag destructiveHint=true and idempotentHint=true, but the description supplies the costly context behind them: in-place is an atomic rename preserving format and name so code references stay valid, out-dir mirrors folder structure but changes extensions so references must be updated. It also discloses the safety gate (a file is replaced only if it passes the quality check and is smaller), the skip of previously lossily-compressed files, and the runtime cost.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and the sibling alternative before the detail, and the sentence-per-idea structure is dense but readable. A few clauses duplicate what the parameter descriptions already say verbatim in spirit (mode for hero images, avif for old browsers), so it is slightly longer than necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained; the description instead covers the mutation semantics, safety gate, idempotency, skip behavior, and timing that an agent needs to call an 11-parameter destructive write tool responsibly. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so the baseline is 3; the description still adds real meaning the schema does not carry, notably the reference-invalidation consequence of outDir format changes and the guidance that stripMetadata matters for GPS/personal photos. It also restates some schema content (mode thresholds, avif browser support), so it is additive rather than fully compensating.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a specific verb and resource ("Optimises images and writes the results") and immediately contrasts the scope with the sibling tool analyze_images, which "writes nothing." An agent can distinguish the two without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use the sibling instead ("If the user only wants to know how much could be saved, or has not agreed to changes yet"), then gives parameter-selection rules of thumb: visually-lossless for hero images/artwork, lossless for logos and UI assets, stripMetadata before publishing personal photos, drop avif for old browsers. Both the tool choice and the argument choice are covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.1.2- Changed
analyze_images8 fields changed- changed
Input schema / properties / formats / descriptionPrevious value: -"Formats allowed when the format may change. Default all four. Drop avif if the site must support old browsers."New value: +"Formats Optiq may choose when the format may change. Default: all four (fast mode: no avif). Drop avif if the site must support browsers older than Safari 16.4. JPEG is never chosen for images with transparency." - changed
Input schema / properties / keepFormat / descriptionPrevious value: -"Never change the file format. Default: true for in-place writes and analysis, false when writing to outDir."New value: +"Never change the file format (a .png stays PNG). Default: true for in-place writes and for analysis, false when writing to outDir. Set false in analysis to see what converting to WebP/AVIF would save." - changed
Input schema / properties / maxSize / descriptionPrevious value: -"Downscale so the longest side is at most this many pixels."New value: +"Downscale so the longest side is at most this many pixels, e.g. 2560 for camera photos on a website. Smaller images are not enlarged. Allowed in lossless mode too (the resize itself changes pixels)." - changed
Input schema / properties / minQuality / descriptionPrevious value: -"Custom SSIM threshold for perceptual modes, e.g. 0.98."New value: +"Custom SSIM threshold for perceptual modes instead of the mode's default, e.g. 0.98. Higher keeps more detail and saves less. Ignored in lossless mode." - changed
Input schema / properties / minSaving / descriptionPrevious value: -"Keep a file unchanged unless it gets at least this much smaller. Default 0.02."New value: +"Keep a file unchanged unless it gets at least this share smaller (0.05 = 5%). Default 0.02. Raise it to avoid churning files for tiny gains." - changed
Input schema / properties / mode / descriptionPrevious value: -"lossless: pixels verified identical. visually-lossless: SSIM >= 0.99. balanced (default): SSIM >= 0.975. fast: like balanced, no AVIF, fewer encodes. Use visually-lossless for hero images, artwork, product photos."New value: +"How strict the quality check is. lossless: decoded pixels verified identical (JPEG inputs only get metadata changes). visually-lossless: SSIM >= 0.99. balanced (default): SSIM >= 0.975. fast: same threshold as balanced, no AVIF, at most 5 encodes, about 3x faster. Use visually-lossless for hero images, artwork and product photos." - changed
Input schema / properties / paths / descriptionPrevious value: -"Image files and/or folders (searched recursively, dot-folders skipped). Absolute paths recommended."New value: +"Image files and/or folders. Folders are searched recursively for jpg, jpeg, png, webp, avif and gif; dot-folders are skipped. Use absolute paths; relative ones resolve against the server's working directory." - changed
Input schema / properties / stripMetadata / descriptionPrevious value: -"Remove EXIF (including GPS location) and XMP. Recommended before publishing photos."New value: +"Remove EXIF (including GPS location, camera serial) and XMP. Recommended before publishing personal photos. Colour profiles that matter (non-sRGB ICC) and image orientation are preserved either way."
- Changed
optimize_images8 fields changed- changed
Input schema / properties / formats / descriptionPrevious value: -"Formats allowed when the format may change. Default all four. Drop avif if the site must support old browsers."New value: +"Formats Optiq may choose when the format may change. Default: all four (fast mode: no avif). Drop avif if the site must support browsers older than Safari 16.4. JPEG is never chosen for images with transparency." - changed
Input schema / properties / keepFormat / descriptionPrevious value: -"Never change the file format. Default: true for in-place writes and analysis, false when writing to outDir."New value: +"Never change the file format (a .png stays PNG). Default: true for in-place writes and for analysis, false when writing to outDir. Set false in analysis to see what converting to WebP/AVIF would save." - changed
Input schema / properties / maxSize / descriptionPrevious value: -"Downscale so the longest side is at most this many pixels."New value: +"Downscale so the longest side is at most this many pixels, e.g. 2560 for camera photos on a website. Smaller images are not enlarged. Allowed in lossless mode too (the resize itself changes pixels)." - changed
Input schema / properties / minQuality / descriptionPrevious value: -"Custom SSIM threshold for perceptual modes, e.g. 0.98."New value: +"Custom SSIM threshold for perceptual modes instead of the mode's default, e.g. 0.98. Higher keeps more detail and saves less. Ignored in lossless mode." - changed
Input schema / properties / minSaving / descriptionPrevious value: -"Keep a file unchanged unless it gets at least this much smaller. Default 0.02."New value: +"Keep a file unchanged unless it gets at least this share smaller (0.05 = 5%). Default 0.02. Raise it to avoid churning files for tiny gains." - changed
Input schema / properties / mode / descriptionPrevious value: -"lossless: pixels verified identical. visually-lossless: SSIM >= 0.99. balanced (default): SSIM >= 0.975. fast: like balanced, no AVIF, fewer encodes. Use visually-lossless for hero images, artwork, product photos."New value: +"How strict the quality check is. lossless: decoded pixels verified identical (JPEG inputs only get metadata changes). visually-lossless: SSIM >= 0.99. balanced (default): SSIM >= 0.975. fast: same threshold as balanced, no AVIF, at most 5 encodes, about 3x faster. Use visually-lossless for hero images, artwork and product photos." - changed
Input schema / properties / paths / descriptionPrevious value: -"Image files and/or folders (searched recursively, dot-folders skipped). Absolute paths recommended."New value: +"Image files and/or folders. Folders are searched recursively for jpg, jpeg, png, webp, avif and gif; dot-folders are skipped. Use absolute paths; relative ones resolve against the server's working directory." - changed
Input schema / properties / stripMetadata / descriptionPrevious value: -"Remove EXIF (including GPS location) and XMP. Recommended before publishing photos."New value: +"Remove EXIF (including GPS location, camera serial) and XMP. Recommended before publishing personal photos. Colour profiles that matter (non-sRGB ICC) and image orientation are preserved either way."
2 tool updates
- First observed
analyze_images - First observed
optimize_images
TDQS
Scored across 2 tools
The two tools have clearly distinct purposes: analyze_images is explicitly read-only and writes nothing, while optimize_images writes results. Descriptions reinforce the boundary and cross-reference each other, so an agent should never confuse them.
Both tool names follow a consistent verb_noun snake_case pattern (analyze_images, optimize_images). The naming is predictable and directly reflects the action performed.
Two tools is slightly thin for a typical server, but for this domain the preview/apply pair is well-scoped and each tool earns its place. A small number is reasonable because the second tool subsumes configuration via arguments rather than separate endpoints.
The surface covers both analysis and write operations, with in-place and out-dir modes, format handling, and safety checks. Minor gaps remain, such as an explicit undo/restore or backup-listing capability, but core workflows are fully covered.
Maintenance
Related MCP Connectors
Image processing over MCP: compress and split images, generate images and QR codes.
MCP server for Qwen Image 3 AI image generation
Official faktry MCP server: generate and edit images, video, audio, and PDFs.
MCP registry & directory: search, find & install 31k+ MCP servers & tools. Catalog and marketplace.
Related MCP Servers
- AlicenseCqualityCmaintenanceMCP server for local compression of various image formats138 npm34MIT
- AlicenseNot gradedqualityFmaintenanceAn MCP server providing tools for image processing operations166 PyPI334PythonMIT
- AlicenseNot gradedqualityCmaintenanceA lightweight MCP server for image processing and cloud uploads that automates resizing, converting, optimizing, and uploading images to services like AWS S3, Cloudflare R2, and Google Cloud Storage.28 npm18MIT
- AlicenseNot gradedqualityDmaintenanceA TypeScript-based MCP server that enables image editing operations including brightness adjustment, cropping, and compression through natural language commands.1ISC