Skip to main content
Glama

pictor-mcp

A secure Model Context Protocol server for image operations, packaged as a hardened Docker image.

Point any MCP client at it and the model can inspect, convert, resize, compress, crop, rotate, watermark, background-remove and batch-process images — and verify its own edits afterwards.

mkdir -p input output
cp .env.example .env            # set PICTOR_AUTH_TOKEN
docker compose up -d            # pulls the published image; nothing is built

The server is then at http://127.0.0.1:8077/mcp.


Contents


Related MCP server: mcp_images

Why this one

It is a file primitive, so it is treated like one. An image server that will read any path an agent names is arbitrary-file-read; one that writes any path is arbitrary-file-write. Reads are confined to configured roots, writes to a single output root, symlink escapes and traversal are refused, and the error never reveals whether a forbidden path exists.

It survives hostile images. Decompression bombs, over-long axes, animation amplification and truncated files are rejected before pixel data is allocated.

It returns what every client can use. Each result carries a self-contained text summary, machine-readable structuredContent, an optional inline image for vision models, and a resource link — so the same call works in a terminal, a chat UI, or an agent loop.

It speaks every MCP revision. The MCP Python SDK negotiates per connection: modern stateless 2026-07-28 requests and legacy initialize-handshake clients are served from the same endpoint with no compatibility flag.

It composes. image_transform runs an ordered operation list in one call, so an agent does not pay a round trip and a base64 transfer per step.


Quick start

The compose files pull a prebuilt image from GHCR, so there is nothing to compile:

git clone https://github.com/TheRealChickenlegs/pictor-mcp.git
cd pictor-mcp

mkdir -p input output
cp .env.example .env
openssl rand -hex 32            # paste into PICTOR_AUTH_TOKEN in .env

# If your uid or gid is not 1000, put your own in .env (see below)
docker compose up -d

Verify it:

docker pull ghcr.io/therealchickenlegs/pictor-mcp:latest   # confirm the tag exists
curl -s http://127.0.0.1:8077/healthz                      # {"status":"ok"}
docker compose logs -f pictor-mcp
docker compose exec pictor-mcp python -m pictor_mcp --check # resolved config, secrets redacted

Put images in ./input, and the server writes results to ./output.

The default port mapping is 127.0.0.1:8077:8077, so nothing off-host can reach it. To expose it on your LAN, see Exposing beyond localhost.

File ownership

The container runs as PUID:PGID, set in .env and defaulting to 1000:1000. Point them at your own identity and everything the server writes to ./output belongs to you, and ./input is readable without loosening anything:

id -u    # e.g. 1000  ->  PUID
id -g    # e.g. 1000  ->  PGID

That is why the quick start above needs no chown. If you would rather not change the container's identity, the other direction works too:

sudo chown -R 1000:1000 input output    # match the container's default

Getting this wrong is the one setup error you are likely to hit, and it is deliberately loud rather than silent — the server refuses to start rather than booting and failing every call:

configuration error: output root /data/output is not writable by uid 1000,
gid 1000: Permission denied. In Docker the container user must match the owner
of the mounted host directory ...

Do not set PUID or PGID to 0. Running as root would negate cap_drop, no-new-privileges and the read-only root filesystem, and it is unnecessary: /data/output inside the image is writable by any uid precisely so that this choice is free.

Which compose file

File

What it does

docker-compose.yml

CPU image. The default; docker compose up -d.

docker-compose.gpu.yml

Overlay: switches to the CUDA image and passes the GPU through.

docker-compose.ml.yml

Overlay: the ML image (CUDA + background removal, u2net baked in).

docker-compose.build.yml

Overlay: build from this checkout instead of pulling. BUILD_TARGET picks the variant, LOCAL_IMAGE_TAG names the result.

Overlays are combined with repeated -f, and they only add — the hardening, volumes and environment all come from the base file, so a variant cannot drift from it:

# GPU
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d

# ML (background removal); do not combine with the GPU overlay, they use the
# same host port and both request the GPU
docker compose -f docker-compose.yml -f docker-compose.ml.yml up -d

Pinning a version

latest follows the default branch. For a reproducible deployment, set exact tags in .env — none of these are read by the server, they are Compose knobs:

IMAGE_TAG=1.0.0
IMAGE_TAG_GPU=1.0.0-gpu
IMAGE_TAG_ML=1.0.0-ml

Every image also carries an immutable sha-<short> tag, which is the one to pin if you want to be certain nothing moves.

Building locally instead of pulling

The CUDA and ML images are several gigabytes, and most of that is PyTorch's NVIDIA wheels. If you are changing the code, build the image where it runs and deploy a stack that names it: nothing is pushed to a registry and nothing is pulled.

make build-gpu     # or: scripts/build_image.sh --target gpu

That builds the gpu stage into the local Docker daemon as pictor-mcp:local-gpu, then starts it with --network none to prove it works offline. Point the stack at it with IMAGE_REPO=pictor-mcp and IMAGE_TAG_GPU=local-gpu; both, and the make targets for the other variants, are in docs/portainer.md, which also covers having Portainer build the image itself and triggering a redeploy from GitLab.

Rebuilds are cheap because the application is installed above every dependency layer in the Dockerfile: a source edit rebuilds one small layer, and a pyproject.toml change reinstalls from a pip cache mount instead of re-downloading. make build does the CPU image in seconds.


Client setup

Every client below needs the same three things: the URL http://<host>:8077/mcp, a bearer token, and nothing else.

DeepSeek Harness (DSH)

Add to your DSH plugin configuration. DSH exposes the tools as mcp__pictor__image_resize and so on.

- id: mcp-pictor
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    serverName: pictor
    transport: streamable-http
    url: http://127.0.0.1:8077/mcp
    headers:
      Authorization: !!js '`Bearer ${process.env.PICTOR_AUTH_TOKEN}`'
    toolCallTimeoutMs: 180000

DSH renders the inline image content blocks, so a vision model can see the result directly. Raise toolCallTimeoutMs above the default 60 s if you process very large images.

OpenCode

In ~/.config/opencode/opencode.json (or a project opencode.json):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "pictor": {
      "type": "remote",
      "url": "http://127.0.0.1:8077/mcp",
      "enabled": true,
      "headers": {
        "Authorization": "Bearer ${PICTOR_AUTH_TOKEN}"
      }
    }
  }
}

For a local (stdio) server instead of Docker:

{
  "mcp": {
    "pictor": {
      "type": "local",
      "command": ["python", "-m", "pictor_mcp"],
      "environment": {
        "PICTOR_TRANSPORT": "stdio",
        "PICTOR_INPUT_ROOTS": "/home/you/images",
        "PICTOR_OUTPUT_ROOT": "/home/you/images/out"
      },
      "enabled": true
    }
  }
}

Hermes agent

Hermes keeps MCP servers in ~/.hermes/config.yaml under mcp_servers, or you can use the CLI: hermes mcp add.

mcp_servers:
  pictor:
    url: "http://127.0.0.1:8077/mcp"
    headers:
      Authorization: "Bearer YOUR_TOKEN_HERE"
    timeout: 180
    connect_timeout: 10

Notes:

  • Omit transport for Streamable HTTP (the default). Add transport: sse only if you deliberately started the deprecated SSE transport.

  • If Hermes' pre-flight content-type probe rejects the endpoint, set skip_preflight: true — the probe expects a content type that a valid Streamable HTTP endpoint does not have to return.

  • For a stdio server, replace url/headers with command: "python" and args: ["-m", "pictor_mcp"], plus an env: block.

Open WebUI

Open WebUI talks to MCP tool servers from its backend (server-side), so no browser-origin configuration is needed.

  1. Admin Settings → Tools → Add Connection / MCP Servers, add a Streamable HTTP entry with URL http://pictor-mcp:8077/mcp (container name, if Open WebUI shares a Docker network) or http://host.docker.internal:8077/mcp from a container to a host port.

  2. Set the auth header to Authorization: Bearer <your token>.

  3. Make the tools available to a model, then ask it to resize an image.

Open WebUI specifics, all of them learned the hard way:

Images display through markdown in the text block, and nothing else works. Reading Open WebUI's process_tool_result (and its MCP client) explains why:

  • Text blocks are joined. Exactly one text block becomes the tool result — two would be wrapped in a JSON object and json.dumps-ed, and the markdown inside would render as escaped characters. This server always returns one.

  • The image content block is dropped. Open WebUI builds its data URI from item["mimeType"], but its own model_dump() renames the field to mime_type first, so it reads data:None;base64,… and gives up. That is Open WebUI's bug and no MCP server using the official SDK models can work around it, which is why PICTOR_INLINE_IMAGES=false is the right setting here — the payload is discarded anyway.

  • resource_link blocks are ignored.

So the display comes from the model repeating the ![name](url) line this server puts at the end of the result text, and the URL has to be browser-reachable. Three settings, in order:

PICTOR_SERVE_OUTPUTS=true        # without this a result carries no URL at all
PICTOR_PUBLIC_BASE_URL=https://pictor.example.com
PICTOR_INLINE_IMAGES=false       # saves the discarded base64 payload

If the model summarises without the link, say so in its system prompt:

When a pictor-mcp tool returns an image, include its ![name](url) line in your reply exactly as given, on its own line.

When no image appears, find which link is broken before changing anything. Each command isolates one hop:

# 1. Does the server think links are on? configurationWarnings should be empty.
docker compose exec pictor-mcp python -m pictor_mcp --check

# 2. Does the public hostname reach this container from outside? 200 means yes.
curl -sS -o /dev/null -w '%{http_code}\n' https://pictor.example.com/healthz

# 3. Did the last tool call actually contain a URL? Look at the tool result in
#    the chat: no "Image URL:" line means PICTOR_SERVE_OUTPUTS is still false.

A 502/404 from step 2 is the common cause: the proxy serves /mcp but not /files/, or the hostname is not routed to this container at all. The /files/ route is exempt from bearer auth — the HMAC signature in the URL is the credential — but it is still subject to the Host check, so the public hostname must be in PICTOR_ALLOWED_HOSTS. The server warns when it is not.

One more thing about links: they expire (PICTOR_URL_TTL_SECONDS, default one hour), so a chat reopened tomorrow shows a broken image. Raise it if you want old conversations to keep rendering.

If tool discovery fails, try PICTOR_STATELESS_HTTP=true (the default) and PICTOR_JSON_RESPONSE=true. Some Open WebUI versions handle plain JSON responses better than SSE streams.

Any other stdio client

The server runs as a normal stdio MCP server:

pip install .
PICTOR_INPUT_ROOTS=/path/to/images PICTOR_OUTPUT_ROOT=/path/to/out python -m pictor_mcp

Or with Docker:

docker run -i --rm \
  --user "$(id -u):$(id -g)" \
  -v /path/to/images:/data/input:ro \
  -v /path/to/out:/data/output \
  -e PICTOR_TRANSPORT=stdio \
  ghcr.io/therealchickenlegs/pictor-mcp:latest

--user is what docker compose sets from PUID/PGID; without it the container uses the image's own uid, and /path/to/out would need to be writable by that account instead. See File ownership.


Tools

15 tools. image_capabilities reports exactly which are usable in your deployment, so an agent can check rather than guess.

Inspect

Tool

Purpose

image_capabilities

Formats, operations, limits, security posture, GPU status.

image_list_inputs

What the server can read, newest first, with the path to pass on.

image_info

Dimensions, format, mode, frames, EXIF, perceptual hashes, dominant colours.

image_compare

SSIM, RMSE, PSNR, changed-pixel ratio and hash distance between two images.

Transform

Tool

Purpose

image_convert

Between JPEG, PNG, WebP, AVIF, TIFF, GIF, BMP, ICO, JPEG 2000, QOI, PPM.

image_resize

By width, height, percentage, or into a box. Six fit modes, six filters.

image_compress

By quality, or search for the best quality under a target size.

image_crop

Pixel box, aspect ratio with gravity, or auto-trim a border.

image_rotate

Any angle, mirror, and EXIF auto-orient.

image_thumbnail

Exact-size thumbnail, cropped by saliency rather than blindly centred.

image_watermark

Text or image, positioned, rotated, tiled, with opacity.

image_background_remove

Colour keying (offline, instant) or an ML model.

image_optimize_web

A width ladder of web variants plus a placeholder and a srcset.

Compose and batch

Tool

Purpose

image_transform

An ordered operation pipeline in one call.

image_batch

The same pipeline across many files, with per-file error isolation.

image_transform

Prefer this when you need more than one step. Operations are validated before any pixels are touched:

{
  "path": "photo.jpg",
  "operations": [
    {"op": "auto_orient"},
    {"op": "resize", "width": 1200, "fit": "cover", "height": 630},
    {"op": "watermark_text", "text": "DRAFT", "opacity": 0.35, "tile": true},
    {"op": "sharpen", "amount": 1.1}
  ],
  "output_format": "webp",
  "quality": 82
}

Available operations: auto_orient, resize, crop, rotate, flip, sharpen, blur, smart_crop, watermark_text, watermark_image, background_remove, feather.

Fit modes: contain (fit inside), cover (fill and crop), fill (stretch), inside (contain, never enlarge), outside (cover, no crop), pad (contain then pad to the exact box).

Image inputs

Every tool accepts exactly one of:

  • path — relative to an allowed root, or absolute inside one. Reads also work from the output root, so you can read back what the server just produced.

  • base64_data — inline bytes, optionally a data: URI.

  • url — HTTP(S), disabled by default and SSRF-guarded when enabled.

url is for images on the public internet and nothing else. The guard refuses private, loopback and link-local addresses whatever the allow-list says, because this server sits inside the network those addresses name — an image that only exists on that network (a chat UI's own file endpoint, a NAS, another container) has to be mounted under PICTOR_INPUT_ROOTS and passed as path. URLs needing credentials are refused too, since the fetch carries none.

Attaching an image in a chat UI

The awkward case: the image lives in your chat UI's own storage, several people upload to it, and nobody is going to copy files into a mount by hand. Mount that storage read-only and it stops being awkward — the file is already there, and image_list_inputs lets the model find it instead of guessing.

For Open WebUI, uploads are written flat into its uploads/ directory as <file-id>_<original-name>, so a file attached in a chat is on disk as 3e6925c9-9b74-4437-ad9d-a246c127592a_Chickenlegs.png:

# where Open WebUI keeps its data, on the host:
docker inspect open-webui --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
# pictor-mcp stack: add the store as a second read-only input root
volumes:
  - ${DOCKER_PATH}/pictor/input:/data/input:ro
  - /path/to/open-webui/data/uploads:/data/uploads:ro

environment:
  PICTOR_INPUT_ROOTS: /data/input,/data/uploads
  PICTOR_SERVE_OUTPUTS: "true"     # so the result renders back in the chat

The model then calls image_list_inputs (optionally pattern="*.png"), sees the newest attachments with the exact path to use, and converts one:

image_list_inputs(pattern="*.png")
image_convert(path="3e6925c9-..._Chickenlegs.png", target_format="webp")

Worth knowing before you mount it: every user's uploads become files that anyone who can call the tools may read or enumerate. That is the same group of people who can already see those images in the chat UI, but it is a filesystem promise rather than a per-user API one. image_list_inputs skips dotfiles, symlinks and anything that is not a regular file, and never leaves the configured roots.


How results come back

One call, four representations of the same artefact — pick whichever your client understands:

// 1. text — always present, self-contained
"Image resize completed.\nInput: 4000x3000 JPEG 3.1 MB\nOutput: 1200x900 WEBP 142.3 KB -> resized/photo-w1200.webp\nSize: 3.1 MB -> 142.3 KB (95.4% smaller)",

// 2. image — inline, for vision models (when return_image is true and it fits)
{"type": "image", "data": "<base64>", "mimeType": "image/webp"},

// 3. resource_link — for clients that resolve MCP resources
{"type": "resource_link", "uri": "pictor://outputs/resized/photo-w1200.webp", ...},

// 4. structuredContent — for programmatic callers
{
  "ok": true,
  "operation": "image_resize",
  "outputs": [{
    "name": "photo-w1200.webp", "path": "resized/photo-w1200.webp",
    "mimeType": "image/webp", "format": "webp", "byteSize": 145715,
    "width": 1200, "height": 900, "sha256": "…", "url": "http://…/files/…"
  }],
  "sizeChange": {"inputBytes": 3251840, "outputBytes": 145715, "savedPercent": 95.52}
}

Base64 never appears twice: if the image is inlined, it is not also in the JSON.

Errors are readable and machine-actionable. A refused path returns is_error: true, a plain message (path is outside the configured input roots), and a stable code in structuredContent.error.code. Messages never contain host paths or library internals, so they are safe to show a model.


GPU acceleration

The CPU image is the default and needs no GPU. If you have an NVIDIA card, switch to the CUDA image with an overlay; acceleration is then detected automatically.

docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d

Requires the NVIDIA Container Toolkit and a driver visible to nvidia-smi. Sanity-check the host before blaming the image:

docker run --rm --gpus all nvidia/cuda:12.6.0-base-ubuntu22.04 nvidia-smi

The GPU tags are :gpu and :<version>-gpu; the overlay selects them for you, and IMAGE_TAG_GPU pins an exact one.

The CUDA line must cover your card's architecture. The image installs a CUDA 12.8 build (cu128), which carries kernels for Turing through Blackwell (RTX 50-series). A card newer than the build will load, appear available, and then fail every kernel launch. The server detects this at startup, disables the GPU, and says so in plain terms:

GPU acceleration unavailable: RTX 5060 Ti (torch ..., cuda ...) is sm_120, which
this PyTorch build has no kernels for; it supports sm_50, ..., sm_90. This build
predates that architecture. Rebuild with a CUDA 12.8 or newer wheel index ...

The same text appears in image_capabilities, so an agent can report it. To change it, override the index at build time — and note the coupling, since an index only helps if it still publishes wheels for the base image's Python:

build:
  args:
    TORCH_INDEX_URL: "https://download.pytorch.org/whl/cu129"

Check a pairing before committing to it:

curl -s https://download.pytorch.org/whl/cu128/torch/ | grep -o 'cp3[0-9]*' | sort -u

What actually speeds up, and what does not:

  • Resampling (resize) runs on the GPU via torch.nn.functional.interpolate. It is only used above PICTOR_GPU_MIN_PIXELS (default 4 MP), because below that the host↔device copy costs more than the resize.

  • Encoding stays on the CPU. libjpeg-turbo and libwebp have no CUDA path in Pillow and are already fast.

  • Lanczos is approximated by bicubic with antialiasing, since torch has no Lanczos kernel. The result notes say so when it happens.

  • Background removal with method: "ml" uses onnxruntime-gpu in the ml image.

The GPU is an accelerator, never a requirement. On startup the server resizes a test image on both paths and compares them; if they disagree beyond tolerance the backend disables itself and logs why. A missing driver, an out-of-memory card or an unsupported pixel format degrades throughput, never correctness.

# Ask the server what it is actually using
# (call the image_capabilities tool, or read the startup log line)
docker compose exec pictor-mcp python -m pictor_mcp --check | grep -i accel

The ml image

Adds rembg + onnxruntime-gpu on top of the CUDA image, with the u2net segmentation weights baked in at build time. That baking is the point: the container needs no network at runtime, which is what makes it usable on an isolated internal network.

docker compose -f docker-compose.yml -f docker-compose.ml.yml up -d

It is substantially larger (roughly a gigabyte of model and ONNX runtime on top of the CUDA wheels), so use it only if you actually need ML background removal. The CPU image can already remove a flat background exactly and instantly with method: "color", which covers the usual product or logo shot.

Only u2net ships inside the image, so only that model works offline. Any other PICTOR_BG_MODEL is downloaded on first use and needs a writable or pre-seeded directory at U2NET_HOME — see the commented volume in docker-compose.ml.yml.

Do not combine the ml and gpu overlays: they publish the same host port and both request the GPU.

Building from source

For a local modification, add the build overlay. BUILD_TARGET selects the Dockerfile stage and LOCAL_IMAGE_TAG names the result, so a CUDA build cannot land under the CPU tag:

BUILD_TARGET=gpu LOCAL_IMAGE_TAG=local-gpu docker compose \
  -f docker-compose.yml -f docker-compose.gpu.yml -f docker-compose.build.yml \
  up -d --build

make build-gpu (and make up-gpu) wrap exactly that, plus a post-build check that the image starts with no network. See Building locally instead of pulling.


Security

Read SECURITY.md for the full threat model. The short version:

Risk

Control

Arbitrary file read

All reads confined to PICTOR_INPUT_ROOTS + the output root. Traversal, symlink escapes, absolute paths and special files (FIFOs, devices) refused.

Arbitrary file write

All writes confined to PICTOR_OUTPUT_ROOT, written atomically with O_NOFOLLOW.

TOCTOU / symlink swap

Files are opened with O_NOFOLLOW and re-verified through /proc/self/fd after opening.

Decompression bombs

Pixel, per-axis, frame and file-size ceilings enforced before allocation.

SSRF via URL input

Off by default. When on: scheme/port/host allow-lists, all resolved addresses must be globally routable, connection pinned to the validated IP, per-hop redirect validation, streaming size cap.

DNS rebinding

Host and Origin headers validated on every route, including health and file serving.

Unauthenticated access

Optional bearer token, compared in constant time. Fail-closed configuration: serving files without a credential refuses to start.

Metadata leakage

EXIF, GPS and ICC stripped by default.

Stored XSS via output files

Served content types are allow-listed to images; anything else is an opaque attachment, every response carries a sandbox CSP, and output_name cannot choose its own extension.

Unbounded intermediate allocation

cover/outside resizes and saliency thumbnails bound the scaled bitmap, not just the result; image_compare processes in strips so memory is independent of image size.

Container breakout

Non-root runtime (PUID:PGID, default 1000:1000, never 0), read-only root filesystem, all capabilities dropped, no-new-privileges, PID and memory limits.

Resource exhaustion

Concurrency cap, per-call timeouts, request body cap, batch limits.

Secret leakage in logs

Startup secrets are redacted; stdout is never used for logging (it is the stdio protocol channel).

Exposing beyond localhost

The default port mapping binds to loopback, so nothing off-host can reach the server. Making it reachable is a three-part change, and doing only the first part is the mistake worth avoiding.

1. Bind the port. The published port is controlled by BIND_ADDRESS in .env, which is easier to switch back than editing the mapping:

# .env - reachable from anything that can route to this host
BIND_ADDRESS=0.0.0.0

The ports: block in docker-compose.yml also lists the equivalent mappings as commented alternatives, including binding one specific interface (192.168.1.10:8077:8077), which is the safest option when the host has several addresses (VPN, docker0, a second NIC).

2. Set the allow-lists to match, or browser-originated requests are refused by design. The defaults name only loopback and the compose service name:

PICTOR_ALLOWED_HOSTS=192.168.1.10:8077,pictor.internal:8077
PICTOR_ALLOWED_ORIGINS=http://192.168.1.10:8077

Non-browser MCP clients (DSH, OpenCode, Hermes, Open WebUI's backend) send no Origin header and are unaffected either way, which is why the loopback default is safe. A browser-based client is the case that needs the allow-list.

Both lists accept a small pattern grammar: a bare host or host:port matches exactly, host:* matches that host on any port, *.example.com matches any subdomain on any port (never the apex itself), and * matches everything — only sane behind a hardened proxy. PICTOR_ALLOWED_ORIGINS takes the same forms with an optional scheme, e.g. https://*.example.com. These patterns are interpreted by this server; the MCP SDK's weaker built-in Host check is disabled so there is only one answer to "is this request allowed?".

3. Always set PICTOR_AUTH_TOKEN once anything but you can reach the port. Anyone who can reach an unauthenticated instance has the full tool surface, which reads and writes files.

Better still, terminate TLS in a reverse proxy and set PICTOR_ENABLE_HSTS=true; the server speaks plain HTTP by design, so put Caddy/nginx/Traefik in front if the network is not trusted. When the proxy makes the external host or port differ from the bind address, also set PICTOR_PUBLIC_BASE_URL so generated file links point somewhere useful.

Put the public hostname in the allow-list, and give it a port wildcard. Requests that arrive through the proxy carry the public Host header, not the internal one, so a domain that is not listed is refused — and because the MCP client usually talks to pictor-mcp:8077 directly over the docker network while the browser fetches generated files through the proxy, the same server can work for tools and fail for every image:

PICTOR_ALLOWED_HOSTS=127.0.0.1:*,localhost:*,pictor-mcp:*,pictor.example.com:*

A bare hostname also matches :80 and :443, since those name the same authority a proxy may forward. Any other port needs the :* form — which is why the example above uses it.

Connecting from another container

This is not the same as exposing the port, and the port mapping is irrelevant to it: containers on one Docker network reach each other by service name on the container's own port, never through the host's published port. A client that calls http://pictor-mcp:8077/mcp sends Host: pictor-mcp:8077, so the PICTOR_ALLOWED_HOSTS default includes pictor-mcp:* and this works with no extra configuration.

Renaming the service in docker-compose.yml renames the Host header too, so update the allow-list to match. If a client is refused with

rejected request: host header 'X' is not allowed

then X is the value to add to PICTOR_ALLOWED_HOSTS — an aggregator that reaches the server through a reverse proxy, or by a LAN name, sends that name instead. The rejection is logged before authentication is checked, so a Host rejection is not an indication that the token is wrong; fix the Host list first, then confirm the token.

Generated file URLs

With PICTOR_SERVE_OUTPUTS=true, results include a URL so a web UI can render the image. Those URLs are not bearer-authenticated — they carry an HMAC signature with an expiry, scoped to one file, which is what lets a browser <img> tag work without the API token:

https://pictor.example.com/files/1789692769.auyHow_iQuQkfvL_Ta9YJw/converted/photo.png
                            └──────── expiry . signature ────────┘

The credential is a path segment, not a query parameter, and that is deliberate. A signed URL has to survive being copied out of a tool result by a language model and pasted into a reply, then rendered through markdown and HTML by a chat UI, then forwarded by a reverse proxy. ?e=…&s=… lost to all three: models drop what reads as noisy parameters, HTML escaping turns & into &amp;, and proxies rewrite or strip queries. Anything that keeps the path keeps the credential, and there is no & to escape. If a link is refused, the container log says which of the reasons it was.

  • The server refuses to start with PICTOR_SERVE_OUTPUTS=true unless PICTOR_AUTH_TOKEN or PICTOR_URL_SECRET is set, so this can never silently become an open directory of everything the server has produced.

  • Set PICTOR_PUBLIC_BASE_URL when the externally visible host/port differs from the bind address (reverse proxy, different published port), or the generated links will point at 127.0.0.1:<PICTOR_PORT>.

  • PICTOR_URL_TTL_SECONDS (default 3600) bounds how long a leaked link works.

  • The /files/ route needs the public hostname in PICTOR_ALLOWED_HOSTS, with a port wildcard if the proxy forwards anything but 80/443. A link that is refused returns 403 with no image, so the browser shows "image unavailable"; the container log now names the file and the reason, which is where to look first when a chat UI shows a broken picture.

  • The result text ends with the exact ![name](url) line to paste into a reply, because that is how a chat UI ends up displaying it — see Attaching an image in a chat UI and the Open WebUI notes. Setting PICTOR_PUBLIC_BASE_URL while this is off is inert, and the server warns about it at startup.


Configuration

Every setting is an environment variable prefixed PICTOR_. Full annotated list with defaults: .env.example. Validate a deployment without starting it:

docker compose exec pictor-mcp python -m pictor_mcp --check

The most consequential ones:

Variable

Default

Notes

PICTOR_TRANSPORT

stdio (streamable-http in Docker)

stdio, streamable-http or sse.

PICTOR_HOST / PICTOR_PORT

127.0.0.1 / 8077

Loopback by default outside Docker.

PICTOR_INPUT_ROOTS

/data

Read roots, comma-separated, absolute.

PICTOR_OUTPUT_ROOT

/data/output

The only writable directory. Must not overlap an input root.

PICTOR_AUTH_TOKEN

(empty)

Required for HTTP once reachable off-host. Minimum 16 characters.

PICTOR_ALLOW_NET_FETCH

false

Enables URL inputs, with the SSRF guard.

PICTOR_SERVE_OUTPUTS

false

Signed URLs for generated files. Set true for a chat UI to display results.

PICTOR_PUBLIC_BASE_URL

(empty)

External base URL for generated links. Its host must be in PICTOR_ALLOWED_HOSTS.

PICTOR_INLINE_IMAGES

true

Inline image in results. false for Open WebUI, which discards it.

PICTOR_STATELESS_HTTP

true

Best client compatibility.

PICTOR_MAX_PIXELS

64000000

Per-frame decompression-bomb ceiling.

PICTOR_MAX_ANIMATION_PIXELS

128000000

Ceiling on width × height × frames.

PICTOR_OP_TIMEOUT_SECONDS

120

Cooperative budget, checked between pipeline steps and quality probes.

PICTOR_HTTP_ACCESS_LOG

false

uvicorn access log; off so signed URLs are not written to logs.

PICTOR_MAX_CONCURRENCY

4

Concurrent operations; size with mem_limit.

PICTOR_STRIP_METADATA

true

Strip EXIF/GPS/ICC from outputs.

PICTOR_ALLOWED_HOSTS

loopback + pictor-mcp:* in Docker

Host headers accepted. Add a name if a client is refused; see Connecting from another container.

PICTOR_GPU

off (auto in the GPU image)

auto, off or torch.

Invalid values make the server refuse to start rather than fall back to a less safe default.

Separately, section 0 of .env.example holds the Compose-only settings. They have no PICTOR_ prefix precisely so they cannot be mistaken for server options:

Variable

Default

Notes

IMAGE_REPO

ghcr.io/therealchickenlegs/pictor-mcp

Registry path, no tag.

IMAGE_TAG

latest

CPU image tag. Pin e.g. 1.0.0.

IMAGE_TAG_GPU

gpu

CUDA image tag, e.g. 1.0.0-gpu.

IMAGE_TAG_ML

ml

ML image tag, e.g. 1.0.0-ml.

BIND_ADDRESS

127.0.0.1

Host interface the port binds to. 0.0.0.0 for LAN.

PUID / PGID

1000 / 1000

uid:gid the container runs as. Set to id -u / id -g so ./output files are yours. Never 0.


Running without Docker

Requires Python 3.10+.

python -m venv .venv && source .venv/bin/activate
pip install .                 # or ".[gpu]", ".[bg]", ".[all]"

PICTOR_TRANSPORT=stdio \
PICTOR_INPUT_ROOTS="$HOME/Pictures" \
PICTOR_OUTPUT_ROOT="$HOME/Pictures/out" \
python -m pictor_mcp

For an HTTP server natively, set PICTOR_TRANSPORT=streamable-http and PICTOR_HOST=127.0.0.1 (a non-loopback host with no explicit PICTOR_ALLOWED_HOSTS accepts any Host header — pair the two).


Container images

Three variants are published to the GitHub Container Registry on every push to the default branch and every v* tag:

Tag

Contents

Platforms

ghcr.io/therealchickenlegs/pictor-mcp:latest

CPU, ~180 MB

linux/amd64, linux/arm64

ghcr.io/therealchickenlegs/pictor-mcp:gpu

CPU + PyTorch CUDA wheels

linux/amd64

ghcr.io/therealchickenlegs/pictor-mcp:ml

GPU + rembg + onnxruntime-gpu, u2net baked in

linux/amd64

Version tags are added alongside (1.0.0, 1.0, 1.0.0-gpu, …), plus an immutable sha-<short> tag per commit. The CUDA images are amd64-only because PyTorch does not publish linux/arm64 wheels for the CUDA index they install from. Images carry a signed build-provenance attestation and an SBOM.

The compose files already point at these tags, so docker compose up -d pulls rather than builds. Override IMAGE_REPO if you mirror them elsewhere, and IMAGE_TAG* to pin versions.

A published image reports the version baked into it, so you can always tell what you are running:

docker run --rm ghcr.io/therealchickenlegs/pictor-mcp:latest python -m pictor_mcp --version

Development

python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

ruff check src tests
ruff format src tests
pytest -q

The suite (≈400 tests) covers path confinement and symlink escapes, SSRF classification of internal address ranges, decompression bombs, format allow-listing, encoder fallbacks, pipeline semantics, and both transports end-to-end — including a live HTTP server for the auth, DNS-rebinding and signed URL behaviour.

Checks

Pull requests run five jobs, all of which must pass:

Job

What it enforces

Lint, format and syntax

ruff check (which parses every file, so syntax errors surface first), ruff format --check, and compileall.

Lint the workflows

actionlint, whose image bundles shellcheck and pyflakes, so the shell and Python embedded in the workflows are checked too.

Tests

The full suite on Python 3.10, 3.11, 3.12 and 3.13 — the declared floor, the version the image ships, and the newest the SDK supports.

Build and install the distribution

Builds the sdist and wheel, installs the wheel into a clean venv, runs the console script, and smoke-tests the installed server over stdio.

Deployment files

Parses docker-compose.yml, the GPU overlay and .env.example and pushes them through the real config parser, so a renamed setting or a lost hardening flag fails the build.

CodeQL runs security-extended analysis on pushes, pull requests and weekly. Dependabot keeps the actions, Python dependencies and base image current.

Layout:

src/pictor_mcp/
├── config.py          environment parsing, fail-loud
├── server.py          transports, middleware, wiring
├── outputs.py         result envelope
├── models.py          public result schema
├── errors.py          error taxonomy with stable codes
├── security/          path jail, limits, SSRF guard, auth, concurrency
├── imaging/           formats, loader, ops, encode, pipeline, analysis
├── backends/          pluggable CPU/CUDA resampling
└── tools/             the 15 MCP tools

License

MIT. See LICENSE.

Available Tools

14 tools
image_background_removeRemove an image backgroundA

Make the background transparent. method='color' keys out a flat border colour (instant, offline, best for product/logo shots); method='ml' uses a U^2-Net model for arbitrary backgrounds; method='auto' picks ML when the model is installed.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch.
pathNoPath to the image.
modelNoML model name: u2net, u2netp, isnet-general-use, silueta.u2net
methodNoauto, color or ml.auto
featherNoSoften the resulting alpha edge by this many pixels.
softnessNoWidth of the soft alpha ramp at the edges.
toleranceNoColour distance treated as background (color method).
backgroundNoComposite onto this RGB colour instead of leaving transparency.
base64_dataNoInline base64 image.
output_nameNoOverride the output filename.
return_imageNoEmbed the result inline.
output_formatNoOutput codec; must support alpha (png, webp, avif, tiff).png
return_base64NoInclude base64 in the JSON result.
edge_connectedNoOnly remove background regions touching the border, protecting interior matches.
strip_metadataNoStrip metadata from the output.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It adds material behavior: color is instant and offline, ml relies on a U^2-Net model, and auto falls back to ML only when installed. This reveals performance and dependency traits beyond what JSON schema could convey, though it could note the ML model download requirement more explicitly.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero waste. The core action is front-loaded, and the method guidance is packed efficiently in the second sentence with parenthetical tradeoffs. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 15-parameter tool with an output schema present, the description covers the central ambiguity — method selection — thoroughly. The presence of an output schema relieves the need to describe return values, and the many decorative parameters (feather, softness, tolerance, background) are fully documented in the schema. The main unstated gap is ML model installation requirements.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so all 15 parameters are documented structurally. The description adds real semantic value not in the schema by explaining what the method values (color/ml/auto) actually do and their tradeoffs, which the schema merely lists as 'auto, color or ml.' That elevation of the key decision parameter justifies above-baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with the specific action 'Make the background transparent,' which is a clear verb+resource statement that the title reinforces. Among 14 siblings (resize, crop, rotate, transform, convert, etc.), background removal is a unique purpose, so there is no ambiguity about what this tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives concrete method-selection guidance: 'color' for flat border product/logo shots (instant, offline), 'ml' for arbitrary backgrounds, and 'auto' picking ML when the model is installed. This is clear when-to-use context for the internal methods, though it doesn't explicitly name sibling exclusions — not strictly needed since the purpose is unique among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_batchApply a pipeline to many imagesA

Run the same operation list across every image in a directory, a glob, or an explicit list of paths. Each file is independent: a failure is reported per file and does not abort the run. Use a {name} placeholder in output_name for per-file suffixes.

ParametersJSON Schema
NameRequiredDescriptionDefault
globNoGlob pattern inside an input root, e.g. 'photos/**/*.jpg'.
limitNoMaximum files to process in this call.
pathsNoExplicit list of input paths.
qualityNoLossy quality for every output.
directoryNoDirectory (inside an input root) to process recursively.
operationsYesOrdered operations applied to every file.
output_formatNoOutput codec; defaults to each input's own format.
strip_metadataNoStrip metadata from every output.
return_first_imageNoEmbed only the first output inline, to bound payload size.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
filesNo
notesNo
failedYes
operationNo
processedYes
succeededYes
totalInputBytesYes
totalOutputBytesYes
inlineImageIncludedNo

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden. It usefully discloses that each file is processed independently and that a per-file failure does not abort the run. However, it leaves important side effects unstated, such as whether input files are modified, where output files are written, and how output filenames are derived.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact: three sentences, with the core behavior and failure semantics front-loaded. The structure is efficient and readable, though the final sentence about output_name is misleading and therefore does not fully earn its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The output schema covers the return shape, so explaining return values is unnecessary. However, for a batch file-processing tool, output destination and naming are core operational unknowns, and the only naming hint references a nonexistent parameter. An agent cannot reliably predict whether images are overwritten or where new files will be placed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3 even with no extra parameter guidance. But the description's only additional parameter instruction—'Use a {name} placeholder in output_name'—references a parameter that does not exist in the input schema. This is actively misleading and lowers the score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence clearly names the verb ('Run'), the resource ('operation list'), and the batch scope ('every image in a directory, a glob, or an explicit list of paths'). It also distinguishes this tool from the single-image sibling tools by emphasizing many-image processing across directory/glob/path inputs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use it: whenever the same pipeline must be applied to many images. The per-file failure isolation also tells agents that batch processing may continue despite individual errors. It does not explicitly name single-image alternatives or state when not to use this tool, so it stops short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_capabilitiesImage server capabilitiesA

Report what this image server can do: supported formats, available operations, resource limits, security posture, and whether GPU acceleration is active. Call this first when you are unsure which formats or features are available.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
gpuNo
notesNo
limitsYes
serverYes
formatsYes
backendsYes
protocolYes
securityYes
operationNo
operationsYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden, and it does so by framing the tool as a reporting/inspection operation ('Report what this image server can do...'). This strongly implies a read-only, non-mutating callaine. It also discloses that results include availability details such as GPU acceleration and security posture, which helps an agent anticipate the nature of the response. It does not mention authorization or cost, but those are unlikely to be concerns for a zero-parameter capability query.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no filler. The first sentence front-loads the tool's purpose and enumerates the specific categories of information, and the second gives a concise usage instruction. Every word earns its place, and the description remains scannable for an agent browsing tool definitions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given zero parameters)Skip an output schema, the description is complete: it states what the tool returns, what triggers its use, and why an agent would call it. There is no missing information needed for correct invocation, and the output schema handles return-value details. The description fully covers the contextual needs for this low-complexity tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters and the schema coverage is effectively 100% (the properties object is empty), so there is nothing for the description to add about parameter meaning. Per the baseline rule for zero-parameter tools, a 4 is appropriate; the description adds context about what the capabilities report covers, but parameter semantics is trivially satisfied by the empty schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies a specific verb ('Report') and resource ('this image server'), then enumerates the exact categories of information returned: supported formats, available operations, resource limits, security posture, and GPU acceleration status. This unambiguously distinguishes the tool from the operation-focused sibling tools, which perform mutations or transformations rather than introspecting server capabilities.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives an explicit usage trigger: 'Call this first when you are unsure which formats or features are available.' This is clear and actionable context for when to use the tool. It does not explicitly state when not to use it or name alternatives, but the zero-parameter introspection role is sufficiently distinct from the sibling image-operation tools that the usage intent is unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_compareCompare two imagesA

Measure how different two images are: SSIM, RMSE, PSNR, mean and max pixel difference, the fraction of changed pixels, and perceptual-hash distance. Use it to verify a transform or to spot duplicate images.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNoURL for the first image.
pathNoPath to the first image.
alignNoResize the second image to the first's dimensions before comparing.
base64_dataNoInline base64 for the first image.
diff_amplifyNoAmplify the diff visually.
compare_to_urlNoURL for the second image.
compare_to_pathNoPath to the second image.
change_thresholdNoPer-pixel difference counted as a change.
compare_to_base64NoInline base64 for the second image.
create_diff_imageNoAlso write a visual difference image (white where identical).

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must convey behavioral traits. It mentions the metrics and the use case but does not disclose how images are sourced (e.g., loading from URLs/paths), any authentication or network requirements, or what happens without both images. It adds some value but misses key operational details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with high information density. It front-loads the core metric list and immediately gives use cases, all in a compact form. No filler or redundancy; every phrase earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (10 params, no required ones) and the presence of an output schema and full parameter schema coverage, the description covers the 'what' and 'why' but not operational details like how to specify images. It could be more complete with notes on default behavior or limitations, but it is mostly sufficient for an agent to select and call it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already explains all 10 parameters. The description does not add extra meaning beyond the schema (e.g., no clarification on 'align' or 'change_threshold'). It lists metric outputs, which is helpful, but does not enhance parameter understanding. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb ('Measure') and resource ('two images'), and enumerates the exact metrics produced (SSIM, RMSE, PSNR, etc.). It also differentiates from siblings by focusing on comparison rather than transformation or manipulation, making it distinct from tools like image_resize or image_transform.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear use cases: verifying a transform or spotting duplicate images. It implies when to use this tool over siblings (e.g., not for resizing) but does not explicitly state when not to use it or name alternatives, like image_transform, for verification. This is adequate but not fully explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_compressCompress an imageA

Reduce an image's file size, either by a quality level or by searching for the best quality that stays under a target size. Reports the exact saving achieved.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
effortNoEncoder effort; higher is slower and smaller.
qualityNoLossy quality 1-100 (higher is better quality and larger).
losslessNoPrefer lossless compression where supported.
base64_dataNoInline image bytes as base64, optionally a data: URI.
output_nameNoOverride the output filename (a safe name is derived if omitted).
return_imageNoEmbed the resulting image inline so vision-capable clients can see it.
return_base64NoInclude the base64 payload in the JSON result for clients without file access.
target_formatNoOutput codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format.
strip_metadataNoRemove EXIF/GPS/ICC metadata. Defaults to the server setting (on).
target_size_kbNoTarget maximum file size in kilobytes; quality is searched to fit.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the core action and outcome (file size reduction, saving report) but does not mention whether it is destructive, whether it overwrites inputs, or what happens to the original file. It also omits details about input sources (URL/path/base64) beyond what the schema already covers.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with zero filler, front-loaded with the primary action and then the two modes. The saving report is mentioned clearly. Efficient and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the core behavior but lacks usage differentiation and side-effect disclosure. Given the tool's complexity (12 parameters) and no annotations, an agent might not know when to use this over image_optimize_web or whether it mutates the source. However, the output schema presumably covers return format, so that gap is filled.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so all parameters are documented. The description adds valuable meaning by clarifying the relationship between quality and target_size_kb: that quality is searched to fit a target size. This goes beyond the individual parameter descriptions and aids correct invocation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reduces file size with two specific modes (quality level or target-size search) and reports the saving achieved. This is specific and distinguishes it from siblings like image_resize (which changes dimensions) and image_convert (which changes format).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the two operating modes but gives no guidance on when to choose this tool over alternatives such as image_optimize_web, which likely overlaps in compression functionality. No exclusions or conditions are mentioned, leaving the agent to infer use cases.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_convertConvert image formatB

Convert an image between formats (JPEG, PNG, WebP, AVIF, TIFF, GIF, BMP, ICO, JPEG 2000, QOI, PPM), controlling quality, losslessness and metadata.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
effortNoEncoder effort: WebP method 0-6 or AVIF speed (inverted). Higher is slower and smaller.
qualityNoLossy quality 1-100 (higher is better quality and larger).
keep_iccNoKeep the ICC colour profile even when other metadata is stripped.
losslessNoUse the codec's lossless mode where supported.
backgroundNoRGB colour used to flatten transparency when the target has no alpha channel.
base64_dataNoInline image bytes as base64, optionally a data: URI.
output_nameNoOverride the output filename (a safe name is derived if omitted).
progressiveNoWrite a progressive JPEG.
return_imageNoEmbed the resulting image inline so vision-capable clients can see it.
return_base64NoInclude the base64 payload in the JSON result for clients without file access.
target_formatNoTarget codec, e.g. 'webp', 'png', 'avif'.webp
strip_metadataNoRemove EXIF/GPS/ICC metadata. Defaults to the server setting (on).

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

B3.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the core conversion action and mentions quality/losslessness/metadata controls, but it does not reveal operational traits such as whether the source is mutated, whether output files are written, how the result is delivered, or any restrictions like the PICTOR_ALLOW_NET_FETCH setting. This is a significant gap for a tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that leads with the primary action and resource, lists supported formats, and summarizes the key controllable aspects. Every word earns its place; there is no filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the rich input schema and the presence of an output schema, the description need not repeat parameter details or return values. However, for a tool with 14 parameters and several close siblings, the description is slim on operational context and selection criteria, leaving the agent to infer too much about when and how to invoke it confidently.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, so the baseline of 3 applies. The description adds minimal semantic value beyond the schema by loosely referencing quality, losslessness, and metadata, which maps to a few parameters but does not clarify the rest. Since the schema already documents all 14 parameters thoroughly, no compensation is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Convert an image between formats', then enumerates the supported codecs (JPEG, PNG, WebP, AVIF, etc.). This clearly distinguishes image_convert from its siblings like image_resize, image_crop, and image_rotate, and from image_compress or image_optimize_web, which target size reduction rather than format conversion.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to choose this tool over alternatives such as image_compress or image_optimize_web, nor any exclusions or prerequisites. The only usage signal is implied by the name and title, which is not enough for an agent to distinguish conversion from compression or optimization tasks.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_cropCrop an imageA

Crop by explicit pixel box, by aspect ratio with a gravity anchor, or auto-trim a uniform border.

ParametersJSON Schema
NameRequiredDescriptionDefault
boxNoExplicit (left, top, right, bottom) box in pixels.
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
trimNoAuto-remove a uniform border instead of using box/aspect_ratio.
clampNoClip a box that falls outside the image instead of failing.
gravityNoWhich part to keep: center, top, bottom-left, ...center
qualityNoLossy quality 1-100 (higher is better quality and larger).
base64_dataNoInline image bytes as base64, optionally a data: URI.
output_nameNoOverride the output filename (a safe name is derived if omitted).
aspect_ratioNoCrop to this width/height ratio, e.g. 1.0 for square, 1.7778 for 16:9.
return_imageNoEmbed the resulting image inline so vision-capable clients can see it.
output_formatNoOutput codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format.
return_base64NoInclude the base64 payload in the JSON result for clients without file access.
strip_metadataNoRemove EXIF/GPS/ICC metadata. Defaults to the server setting (on).
trim_toleranceNoColour tolerance for trimming.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the disclosure burden. It does explain the core behavior (three crop strategies and gravity anchoring), but it does not mention side effects, input-source requirements, non-destructiveness, or output behavior. These are not contradictory, but are left to the schema/output schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, well-structured sentence that leads with the action and enumerates the three modes in order. Every word contributes; no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 15 optional parameters, the description is terse: it does not state what happens if multiple crop modes are supplied together (box, aspect_ratio, trim), nor clarify input-source selection. However, the rich per-parameter schema and output schema cover most necessary details.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline applies: the description only groups the parameters into conceptual modes and adds no new syntax or semantic detail beyond what the parameter descriptions already contain.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies the action (crop) and resource (an image) and names three distinct operation modes: pixel box, aspect-ratio with gravity, and uniform-border trim. This makes it readily distinguishable from sibling operations like image_resize or image_rotate.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implicitly tells the agent when to use each mode (precision box, aspect-ratio composition, or auto border trim) but does not explicitly state when not to use the tool or name alternatives. Since context is clear and no exclusions are needed, this is slightly above minimum.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_infoInspect an imageA

Read an image's metadata without modifying it: dimensions, format, colour mode, animation frames, EXIF, perceptual hashes and dominant colours.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
base64_dataNoInline image bytes as base64, optionally a data: URI.
include_exifNoInclude decoded EXIF tags.
include_hashesNoInclude perceptual hashes for duplicate detection.
include_coloursNoInclude the dominant colour palette.
max_exif_entriesNoCap on returned EXIF entries.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
exifNo
imageYes
notesNo
hashesNo
operationNo
dominantColoursNo

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It does disclose the non-destructive nature ('without modifying it'), which is a key behavioral trait. However, it omits other behavioral details such as network fetch requirements (the URL parameter is disabled unless PICTOR_ALLOW_NET_FETCH=true) and potential error cases, so it is only partially transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that front-loads the core purpose and lists the metadata types concisely. There is no wasted text, and it is appropriately sized for the tool's simplicity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has an output schema, so the description does not need to explain return values. The description covers the main purpose and the non-destructive aspect. However, it could mention the multiple input methods (URL, path, base64) but those are documented in the schema, so the description is sufficiently complete for an agent to understand the tool's function.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description does not add any meaning beyond what the schema already provides for parameters; it only lists output categories without linking them to inputs. Thus it meets the baseline without adding value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific action (read metadata) and a specific resource (an image), and explicitly lists the metadata types (dimensions, format, colour mode, etc.). It distinguishes itself from sibling tools, which are largely modification tools (resize, transform, convert), by emphasizing non-modification.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context: it is a read-only inspection tool, implicitly excluding modification operations. However, it does not explicitly name alternative tools or conditions for when to use this tool over siblings like image_capabilities, so it falls short of a 5 but is above a 3.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_optimize_webProduce responsive web variantsA

Turn one source image into a set of web-ready variants at several widths, each optionally constrained to a maximum file size, plus a tiny blurred placeholder and a ready-to-paste srcset. This is the one call to use for 'make this web-ready'.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch.
pathNoPath to the source image.
widthsNoTarget widths, e.g. [480, 960, 1440]. Defaults to a sensible ladder.
qualityNoStarting quality for each variant.
base64_dataNoInline base64 source image.
output_formatNoOutput codec: webp, avif or jpeg.webp
strip_metadataNoStrip metadata from every variant.
create_placeholderNoAlso produce a tiny blurred placeholder and its base64 data URI.
max_bytes_per_variantNoPer-variant byte ceiling; quality is searched down per width to fit.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden of behavioral disclosure. It does disclose key behaviors: it produces multiple variants, optionally constrains file size, creates a placeholder, and generates a srcset. However, it doesn't mention whether the source is deleted, whether it fetches remote URLs, or any side effects. The description adds meaningful behavioral context but leaves some operational details undisclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler. The first sentence front-loads the core function and outputs; the second sentence gives a memorable usage rule. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has 9 parameters, an output schema, and no annotations. The description covers the high-level purpose and key outputs, and the output schema presumably documents return values. It doesn't mention input source options (url/path/base64_data) or default behaviors, but the schema covers those. For a complex tool, the description is reasonably complete, though it could note that it accepts multiple source types.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 9 parameters. The description adds a little context by mentioning 'several widths', 'maximum file size', and 'srcset', which maps to widths, max_bytes_per_variant, and output. But it doesn't explain parameter interactions (e.g., how quality relates to max_bytes_per_variant) beyond what the schema already says. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Turn one source image into a set of web-ready variants'), names the concrete outputs (variants at several widths, blurred placeholder, srcset), and explicitly positions it as the go-to call for 'make this web-ready'. This clearly distinguishes it from siblings like image_resize or image_convert.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a clear usage signal: 'This is the one call to use for "make this web-ready"'. It implies this tool is preferred over more basic siblings for responsive web output, though it doesn't explicitly name alternatives or state when not to use it. The context is clear enough for an agent to select it appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_resizeResize an imageB

Resize by width, height, percentage, or to fit a box. Supports contain, cover, fill, inside, outside and pad fit modes, all resampling filters, and optional format change.

ParametersJSON Schema
NameRequiredDescriptionDefault
fitNocontain (fit inside, keep ratio), cover (fill and crop), fill (stretch), inside (contain but never enlarge), outside (cover without cropping), pad (contain then pad)contain
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
widthNoTarget width in pixels.
filterNonearest, box, bilinear, hamming, bicubic, lanczos, or auto.
heightNoTarget height in pixels.
gravityNoAnchor for cover cropping or padding, e.g. center, top, bottom-right.center
percentNoScale by this percentage of the original.
qualityNoLossy quality 1-100 (higher is better quality and larger).
backgroundNoRGBA fill used by pad and rotate.
base64_dataNoInline image bytes as base64, optionally a data: URI.
only_shrinkNoNever enlarge; leave smaller images untouched.
output_nameNoOverride the output filename (a safe name is derived if omitted).
return_imageNoEmbed the resulting image inline so vision-capable clients can see it.
output_formatNoOutput codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format.
return_base64NoInclude the base64 payload in the JSON result for clients without file access.
strip_metadataNoRemove EXIF/GPS/ICC metadata. Defaults to the server setting (on).

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations are entirely absent, so the description carries the full behavioral disclosure burden. It does state supported fit modes, resampling filters, and optional format change, which is meaningful behavioral detail, but it does not disclose side effects, output delivery specifics, network/fetch constraints, or default behavior beyond what appears in the input schema. No contradictions occur.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no redundancy: it front-loads the core resizing methods, then lists supported modes, filters, and format changes. Every phrase earns its place, and no unnecessary words or examples dilute the meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 17 parameters, a 100% schema-covered input schema, and an output schema, the description does not need to re-explain parameters or return values. However, in a family of 13 sibling tools, it does not add enough disambiguating or decision-level context, and it leaves important usage questions (which input source is required with a network fetch, output naming, whether the result is returned as base64 or filename) unanswered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3 even without extra parameter details in the description. The description adds high-level meaning around resizing by width, height, percentage, or fit box, but it does not enrich individual parameter semantics beyond what the schema already documents.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the action (resize), the resource (image), and the main input modes (width, height, percentage, fit box), plus the supported fit modes and optional format change. It conveys what the tool does and this can help distinguish it from siblings such as image_rotate, but it does not explicitly name or contrast sibling tools, so it is a tier below the strongest examples.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no guidance on when to choose image_resize versus sibling tools like image_thumbnail, image_compress, or image_transform, and no mention of prerequisites, complementary source parameters, or when not to use it. The description simply states the action without helping an agent decide among the many image tools in this family.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_rotateRotate or flip an imageA

Rotate by any angle, mirror horizontally or vertically, and/or apply the EXIF orientation so the pixels match what a viewer shows.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
flipNoMirror the image: horizontal, vertical or both.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
angleNoDegrees counter-clockwise.
expandNoGrow the canvas so rotation does not clip corners.
qualityNoLossy quality 1-100 (higher is better quality and larger).
backgroundNoRGBA fill for exposed areas.
auto_orientNoApply the EXIF orientation tag first.
base64_dataNoInline image bytes as base64, optionally a data: URI.
output_nameNoOverride the output filename (a safe name is derived if omitted).
return_imageNoEmbed the resulting image inline so vision-capable clients can see it.
output_formatNoOutput codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format.
return_base64NoInclude the base64 payload in the JSON result for clients without file access.
strip_metadataNoRemove EXIF/GPS/ICC metadata. Defaults to the server setting (on).

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden. It usefully reveals that EXIF orientation can be applied so pixels match a viewer, but it omits other behavioral context (defaults, output side effects, metadata handling) beyond what the schema already states.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence captures the core operations plus the EXIF nuance with no filler. It earns every word and is easy for an agent to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a rich output schema, 100% parameter coverage, and a focused one-sentence description, an agent has enough to understand the operation. The missing usage-guidance nuance is already scored separately, so this remains largely complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description itself adds no parameter detail, but it does not need to; every parameter already has an explicit schema description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific operation set—rotate by arbitrary angle, mirror horizontally or vertically, and apply EXIF orientation—so the tool is clearly distinguished from resize, crop, thumbnail, and watermark siblings. 'Rotate or flip an image' in the title reinforces the exact resource and action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use is implied by the verbs and by the viewer-matching EXIF rationale, but there is no explicit statement of when to choose image_rotate over image_transform or other siblings, nor any exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_thumbnailCreate a thumbnailB

Produce a thumbnail of an exact size, choosing the crop by saliency (attention or entropy) instead of blindly taking the centre.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true.
pathNoPath to the image, relative to an allowed input root (or absolute inside one).
sizeNoExact (width, height) of the thumbnail.
methodNoattention (edge energy), entropy (local detail) or center.attention
qualityNoLossy quality 1-100 (higher is better quality and larger).
sharpenNoUnsharp-mask amount applied after resizing; 0 disables.
base64_dataNoInline image bytes as base64, optionally a data: URI.
output_nameNoOverride the output filename (a safe name is derived if omitted).
return_imageNoEmbed the resulting image inline so vision-capable clients can see it.
max_dimensionNoShorter alternative: fit within a square of this size, keeping ratio.
output_formatNoOutput codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format.
return_base64NoInclude the base64 payload in the JSON result for clients without file access.
strip_metadataNoRemove EXIF/GPS/ICC metadata. Defaults to the server setting (on).

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It only says 'Produce a thumbnail' without mentioning side effects, permissions, whether the original is modified, or if output is returned as a file vs inline. The output schema documents return values but not behavior like overwriting or network access.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence of about 20 words, front-loading the core function and its unique feature. Every word contributes; there is no filler or repetition of schema details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 13 parameters, the description is minimal, but the schema fully documents each parameter and there is an output schema covering return values. The high-level purpose is clear, yet the description lacks usage context (e.g., when to use saliency vs center) and doesn't mention that all parameters are optional. It's adequate but not comprehensive.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all 13 parameters are documented. The description adds minimal context: it references 'exact size' and 'saliency (attention or entropy)' which align with the size and method parameters but don't go beyond the schema. This meets the baseline for high coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb ('Produce') and resource ('thumbnail'), and specifies the key differentiator: saliency-based cropping 'instead of blindly taking the centre.' This distinguishes it from a simple center-crop tool, though it doesn't name sibling tools explicitly. The purpose is unambiguous and specific.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives like image_resize or image_crop. The description implies it's for thumbnails but doesn't state conditions, exclusions, or preferred scenarios. An agent must infer usage from the tool name and parameters.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_transformApply an ordered image pipelineA

Run several operations in one call, in order. Prefer this over chaining single-step tools: it transfers the image once and applies every step server-side. Operations: auto_orient, resize, crop, rotate, flip, sharpen, blur, smart_crop, watermark_text, watermark_image, background_remove, feather.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch (disabled by default).
pathNoPath to the image, inside an allowed input root.
qualityNoLossy quality for the final encode.
losslessNoUse a lossless encode where supported.
operationsYesOrdered list of operations, each with an 'op' field.
base64_dataNoInline base64 image, optionally a data: URI.
output_nameNoOverride the output filename.
return_imageNoEmbed the result inline for vision clients.
output_formatNoOutput codec; defaults to the input format.
return_base64NoInclude base64 in the JSON result.
strip_metadataNoStrip EXIF/GPS metadata from the output.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the behavioral burden. It discloses that operations are applied in order and that the image is transferred once and processed server-side, which is useful performance context. However, it omits details about failure modes, operation limits, or input source constraints, though those are present in the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences. The first sentence states the core purpose, the second explains when to prefer it and lists the supported operations. No words are wasted, and the key differentiator (single transfer, server-side ordering) is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description provides a clear overview of the tool's role and the available operations. While it does not mention constraints like the 32-operation maximum, that information is present in the schema. Given the existence of an output schema, the description does not need to explain return values, making it adequate for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

All parameters have descriptions in the schema (100% coverage), so the baseline is 3. The description lists the available operations, but that list is also encoded in the schema's discriminator and operation definitions. The description adds no detail about parameter formats or defaults beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Run several operations in one call, in order.' This clearly states the tool's function (applying an ordered pipeline) and differentiates it from siblings that perform a single operation, such as image_resize or image_crop. The line 'Prefer this over chaining single-step tools' makes the distinction explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly recommends using this tool over chaining individual transform tools, and explains the benefit (single transfer, server-side execution). It does not explicitly state a when-not scenario, such as 'for a single operation use the dedicated tool,' but the guidance effectively routes the agent to this tool for multi-step processing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

image_watermarkWatermark an imageA

Add a text or image watermark with position, opacity, rotation and optional tiling. Supply either 'text' or 'watermark_path'.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlNohttp(s) URL to fetch.
pathNoPath to the base image.
textNoWatermark text.
tileNoRepeat the watermark across the whole image.
scaleNoLogo width as a fraction of the image's shorter edge.
colourNoText RGB colour.
shadowNoDraw a drop shadow behind text for legibility.
opacityNoWatermark opacity.
paddingNoDistance from the edge in pixels.
qualityNoLossy quality.
spacingNoGap between tiles in pixels.
positionNocenter, top, bottom, left, right, or a corner like bottom-right.bottom-right
rotationNoRotate the watermark, in degrees.
font_sizeNoText size in pixels; defaults to a fraction of the image.
base64_dataNoInline base64 base image.
font_familyNoFont name from the server's font index, e.g. DejaVuSans-Bold.
output_nameNoOverride the output filename.
return_imageNoEmbed the result inline.
stroke_widthNoOutline width for text.
output_formatNoOutput codec; defaults to the input format.
return_base64NoInclude base64 in the JSON result.
strip_metadataNoStrip metadata from the output.
watermark_pathNoPath to a logo/watermark image (mutually exclusive with text).

Output Schema

ParametersJSON Schema
NameRequiredDescription
okNo
inputNoSummary of the source image, when one was read.
notesNo
stepsNo
metricsNoOperation-specific measurements (e.g. comparison scores).
outputsNo
operationYesTool that produced this result.
sizeChangeNo
inlineImageIncludedNoTrue when an image block accompanies this result for vision-capable clients.

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of explaining behavior. It does disclose the core effect (adding a watermark), supported features, and the 'text' vs 'watermark_path' constraint. However, it does not explain what happens if both are supplied, how the source image is selected, or the output behavior, leaving the agent to infer important runtime semantics.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two tight sentences with no filler. It front-loads the action, summarizes key options, and closes with the most important invocation constraint. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is sufficient for basic selection and gives the key either/or constraint, and the rich schema plus output schema reduce the need to restate parameters. However, with zero required parameters and no mention that the base image must be supplied via url, path, or base64_data, an agent could easily construct an invalid call. For a 23-parameter tool, that is a meaningful completeness gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 23 parameters. The description adds some value by highlighting the text/watermark_path mutual exclusivity and grouping position, opacity, rotation, and tiling, but it does not meaningfully enrich the schema's parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific operation ('Add a text or image watermark') on a specific resource (an image), so an agent can tell this is the watermarking tool. It does not explicitly compare against sibling tools, but the verb and object are unambiguous enough to prevent confusion with resize, crop, rotate, or convert.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use case is implied: use this when you need to add a text or image watermark. However, there is no explicit when-not-to-use guidance or mention of alternatives among the 13 sibling tools, so the agent must infer selection from the title and first sentence.

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.

  1. 14 tool updatesv1.0.0
    • First observedimage_background_remove
    • First observedimage_batch
    • First observedimage_capabilities
    • First observedimage_compare
    • First observedimage_compress
    • First observedimage_convert
    • First observedimage_crop
    • First observedimage_info
    • First observedimage_optimize_web
    • First observedimage_resize
    • First observedimage_rotate
    • First observedimage_thumbnail
    • First observedimage_transform
    • First observedimage_watermark

TDQS

A3.6/5.0

Scored across 14 tools

Disambiguation2/5

Several single-step tools overlap heavily with image_transform, which already supports resize, crop, rotate, watermark, and background removal. An agent deciding between image_resize and image_transform with a resize operation, or between image_thumbnail and image_resize, will face real ambiguity. A few tools like image_info and image_compare are distinct, but the manipulation subset is not clearly delineated.

Naming Consistency4/5

All tools share the image_ prefix and use lower_snake_case, giving the set a coherent visual pattern. The second part is usually a verb (resize, convert, compress, crop, rotate, compare), though a few nouns appear (capabilities, info, thumbnail, watermark, batch). This is a minor deviation rather than a chaotic mix.

Tool Count4/5

Fourteen tools is on the higher end but still believable for a general-purpose image manipulation server. The count is justifiable given the range of operations, batch processing, web optimization, and comparison features. It is not bloated, though the redundancy with image_transform makes the number feel slightly larger than necessary.

Completeness5/5

The surface covers a full image lifecycle: inspect, resize, crop, rotate, convert, compress, watermark, remove backgrounds, batch-process, compare, and produce web-ready output. It also includes capabilities reporting and metadata extraction, so there are no obvious dead ends or missing core operations. The composite transform fills in extra operations like sharpen, blur, and feather.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers