MCP server

Image processing as a tool for AI agents. OpticScript ships a Model Context Protocol server. Connect Claude, Gemini or your own agent to it and you are not handing it a handful of fixed filters — you are handing it the whole scripting engine: the model writes an OpticScript script and runs it.

The difference is not one of degree. An MCP server exposing resize, crop and blur can do three things. This one can do anything you can express in OpticScript, and the model combines it itself.


What you can ask

You ask in plain language — the agent picks the tools. The examples below are real runs through the MCP server, on a folder of five holiday photos; the numbers come from those runs.

"Which photos in my holiday folder are duplicates?" The agent calls image_hash on every file. Result: two pairs. IMG_4711.jpg and see_klein.jpg are the same picture — the copy is half the size and recompressed, the distance still 0 bits. cafe.webp and cafe (Kopie).webp likewise. The other photos are 30–33 bits apart, so different pictures.

"Get the lake photos ready for the web: WebP, long side 1200 pixels — and the location must not go online." One run_script with three lines (resize, unsetMeta("exif.GPSInfo"), save as WebP), then read_exif to check. 1600×1067 and 393,718 bytes become 1200×800 and 108,220 bytes, 73 % smaller. The GPS coordinates are gone, copyright and camera data stay. If you don't mention the location, it stays in the file — worth saying.

"Add me as the photographer and the copyright to the café pictures." run_script with mergeMeta("exif", { Artist, Copyright }); read_exif confirms: photographer "Anna Beispiel", copyright "© 2026 Anna Beispiel — alle Rechte vorbehalten".

"Mark the lake photo with my id AB-2026." — and weeks later: "Does this picture carry an id?" run_script with img.mark("AB-2026"), later img.readMark() on a copy a platform cropped square, resized to 640 pixels and recompressed: found, AB-2026. On the winter photo: no id. More under Copyright protection — marking is Pro, checking free.

"Which photo is the sharpest, and are highlights blown anywhere?" analyze_image on each picture, no script needed. The lake photo is by far the sharpest (sharpness 0.027), the winter one 0.005, the café photo 0.0008 — soft-focus, the worst choice for a large print. Blown highlights: none above 0.04 % of the area.

And a programming task

"Make me a contact sheet of all photos in the folder, with file name and capture date under each picture."

There is no ready-made tool for that — the agent writes the script itself: looks up drawText and blendAt with get_api_reference, checks the draft with validate_script and runs it with five inputs. It passes the file names as a parameter, because over MCP the pictures arrive as data, not as files.

Contact sheet of five holiday photos, file name and capture date under each — written by the agent and run through the MCP server

keys.forEach((key, i) => {
  const img = Engine.loadImage(key);
  const s = Math.max(TW / img.width, TH / img.height);       // fill the tile
  const cw = Math.round(TW / s), ch = Math.round(TH / s);
  img.crop(Math.round((img.width - cw) / 2), Math.round((img.height - ch) / 2), cw, ch).resize(TW, TH);
  sheet.blendAt(img, px(x, y), 1.0, Blend.Over);
  const raw = img.getMeta("exif")?.DateTimeOriginal;          // "2017:09:27 14:08:55"
  const date = raw ? raw.slice(0, 10).split(":").reverse().join(".") : "ohne Datum";
  sheet.drawText(names[i], x, y + TH + 24, { size: 18, color: "#e6edf3" });
  sheet.drawText(date,     x, y + TH + 46, { size: 15, color: "#8b98a9" });
});

Excerpt; the whole script (30 lines) is in the repository at mlcprodweb/scripts/mcp_example_kontaktbogen.js. Lake photo: W. Bulach, CC BY-SA 4.0.


The ten tools

Changing images

Tool What for
run_script Run an OpticScript script. //!INPUT: keys map to image files, //!OUTPUT: lands on disk. A script that only measures and answers through console.log needs no output file at all.
validate_script Syntax check without executing and without input images. Returns ok or the error with line and column.

Measuring images — new in 2.5.1, for a simple reason: "is this photo sharp" is not image editing. Before these, a model had to write a script for it and invent a throwaway output image.

Tool What for
analyze_image Dimensions, format, per-channel brightness and spread, sharpness, crushed shadows and blown highlights, transparency, histogram, dominant colours, perceptual hash, EXIF — in one call.
compare_images MSE, PSNR, MAE, largest single difference, how many pixels differ, hash distance. Answers "did my output change" and "is this JPEG quality enough".
read_exif Camera, lens, capture time, orientation, exposure, aperture, ISO, focal length, GPS — plus the provenance our own runs stamp in.
image_hash Perceptual hash of several files with the bit distance to the first. Finds duplicates, survives rescaling and re-encoding.
image_info Just dimensions, format and byte size — without loading the image into the engine.

Finding your way

Tool What for
get_api_reference The JS API reference, optionally filtered by a search term.
list_examples List the bundled example scripts.
get_example Fetch one example's source.

Plus a resource (opticscript://skills) carrying the full scripting guide, and two prompts: author_script writes a script for a described task, fix_script works a broken one against the real API.


Why the order matters

The tools alone are not enough for a model. Without guidance it writes plausible-looking JavaScript using methods that sound like a different imaging library — and the engine does not know them.

That is why the order lives in the author_script prompt:

  1. Read the guide (opticscript://skills) — the full API, the memory rules, the common pitfalls.
  2. Declare the directives — //!INPUT:, //!OUTPUT:, //!PARAM:.
  3. validate_script — the syntax check needs no input images and costs nothing.
  4. Only then run_script with the real paths.

Anyone wiring up the server should know about that prompt. It is the difference between "the model guesses" and "the model delivers".


Wiring it up

The server speaks stdio and ships as mlcos-mcp.

Claude Desktop

Edit the file, then restart Claude Desktop:

  • Windows — %APPDATA%\Claude\claude_desktop_config.json
  • macOS — ~/Library/Application Support/Claude/claude_desktop_config.json
  • Linux — ~/.config/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "mlc-opticscript": {
      "command": "C:\\Program Files\\MLC OpticScript\\mlcos-mcp.exe",
      "args": ["-root", "C:\\Users\\me\\Pictures"]
    }
  }
}

On macOS the path points into the app bundle:

{
  "mcpServers": {
    "mlc-opticscript": {
      "command": "/Applications/MLC OpticScript.app/Contents/MacOS/mlcos-mcp",
      "args": ["-root", "/Users/me/Pictures"]
    }
  }
}

An absolute path is required: Claude Desktop does not start the server in your project directory, and a relative command fails without a visible message.

Claude Code

claude mcp add mlc-opticscript -- /path/to/mlcos-mcp -root ~/Pictures

What -root does

It pins the directory that documentation and example scripts are read from, and doubles as the natural working folder for input and output files. Without it the server walks up from the working directory — which, on a client's desktop, rarely ends up where your images are.

It is built on the MCP organisation's official Go SDK and speaks protocol version 2026-07-28. Every file it produces carries provenance in its metadata — engine version, timestamp and via=mlcos-mcp — so it stays traceable which run created it.


What a session looks like

An agent asked to "turn this screenshot into a framed thumbnail" typically goes:

  1. analyze_image on the file — how big, how bright, does it have transparency?
  2. get_api_reference with query: "resize" — what is the method actually called?
  3. validate_script on the draft — does the syntax hold?
  4. run_script with input and output paths.
  5. compare_images between input and result — did anything happen at all, and how much?

Five calls, and not one of them a guess. Step 5 is the new one: a model can check its own result instead of asserting it.


See also