pictor-mcp
It is an MCP image-operations server that lets models and clients inspect, transform, verify, and batch-process images securely.
Inspect images: dimensions, format, mode, frames, EXIF, perceptual hashes, dominant colours, and compare two images (SSIM, RMSE, PSNR, diff images).
Convert formats: JPEG, PNG, WebP, AVIF, TIFF, GIF, BMP, ICO, JPEG 2000, QOI, PPM with quality/lossless/progressive control.
Resize, compress, crop, rotate, flip, thumbnail (saliency-based), watermark (text or image), and remove backgrounds (colour keying or ML).
Run ordered multi-step pipelines in one call and apply the same pipeline across many files with per-file error isolation.
Generate responsive web variants: width ladders, srcset, placeholders, and per-variant size limits.
Return results in multiple forms: text summaries, inline images for vision models, resource links, and structured JSON.
Verify edits by comparing outputs against inputs, and report server capabilities, limits, security posture, and GPU status.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pictor-mcpResize all images in input to 800px wide and save them to output"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 builtThe 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 -dVerify 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 redactedPut 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 -> PGIDThat 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 defaultGetting 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 |
| CPU image. The default; |
| Overlay: switches to the CUDA image and passes the GPU through. |
| Overlay: the ML image (CUDA + background removal, u2net baked in). |
| Overlay: build from this checkout instead of pulling. |
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 -dPinning 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-mlEvery 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 gpuThat 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: 180000DSH 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: 10Notes:
Omit
transportfor Streamable HTTP (the default). Addtransport: sseonly 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/headerswithcommand: "python"andargs: ["-m", "pictor_mcp"], plus anenv:block.
Open WebUI
Open WebUI talks to MCP tool servers from its backend (server-side), so no browser-origin configuration is needed.
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) orhttp://host.docker.internal:8077/mcpfrom a container to a host port.Set the auth header to
Authorization: Bearer <your token>.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
imagecontent block is dropped. Open WebUI builds its data URI fromitem["mimeType"], but its ownmodel_dump()renames the field tomime_typefirst, so it readsdata: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 whyPICTOR_INLINE_IMAGES=falseis the right setting here — the payload is discarded anyway.resource_linkblocks are ignored.
So the display comes from the model repeating the  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 payloadIf the model summarises without the link, say so in its system prompt:
When a pictor-mcp tool returns an image, include its
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_mcpOr 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 |
| Formats, operations, limits, security posture, GPU status. |
| What the server can read, newest first, with the |
| Dimensions, format, mode, frames, EXIF, perceptual hashes, dominant colours. |
| SSIM, RMSE, PSNR, changed-pixel ratio and hash distance between two images. |
Transform
Tool | Purpose |
| Between JPEG, PNG, WebP, AVIF, TIFF, GIF, BMP, ICO, JPEG 2000, QOI, PPM. |
| By width, height, percentage, or into a box. Six fit modes, six filters. |
| By quality, or search for the best quality under a target size. |
| Pixel box, aspect ratio with gravity, or auto-trim a border. |
| Any angle, mirror, and EXIF auto-orient. |
| Exact-size thumbnail, cropped by saliency rather than blindly centred. |
| Text or image, positioned, rotated, tiled, with opacity. |
| Colour keying (offline, instant) or an ML model. |
| A width ladder of web variants plus a placeholder and a |
Compose and batch
Tool | Purpose |
| An ordered operation pipeline in one call. |
| 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 adata: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 chatThe 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 -dRequires 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-smiThe 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 -uWhat actually speeds up, and what does not:
Resampling (resize) runs on the GPU via
torch.nn.functional.interpolate. It is only used abovePICTOR_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"usesonnxruntime-gpuin themlimage.
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 accelThe 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 -dIt 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 --buildmake 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 |
Arbitrary file write | All writes confined to |
TOCTOU / symlink swap | Files are opened with |
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 |
Unbounded intermediate allocation |
|
Container breakout | Non-root runtime ( |
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.0The 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:8077Non-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 allowedthen 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
&, 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=trueunlessPICTOR_AUTH_TOKENorPICTOR_URL_SECRETis set, so this can never silently become an open directory of everything the server has produced.Set
PICTOR_PUBLIC_BASE_URLwhen the externally visible host/port differs from the bind address (reverse proxy, different published port), or the generated links will point at127.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 inPICTOR_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
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. SettingPICTOR_PUBLIC_BASE_URLwhile 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 --checkThe most consequential ones:
Variable | Default | Notes |
|
|
|
|
| Loopback by default outside Docker. |
|
| Read roots, comma-separated, absolute. |
|
| The only writable directory. Must not overlap an input root. |
| (empty) | Required for HTTP once reachable off-host. Minimum 16 characters. |
|
| Enables URL inputs, with the SSRF guard. |
|
| Signed URLs for generated files. Set |
| (empty) | External base URL for generated links. Its host must be in |
|
| Inline image in results. |
|
| Best client compatibility. |
|
| Per-frame decompression-bomb ceiling. |
|
| Ceiling on width × height × frames. |
|
| Cooperative budget, checked between pipeline steps and quality probes. |
|
| uvicorn access log; off so signed URLs are not written to logs. |
|
| Concurrent operations; size with |
|
| Strip EXIF/GPS/ICC from outputs. |
| loopback + | Host headers accepted. Add a name if a client is refused; see Connecting from another container. |
|
|
|
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 |
|
| Registry path, no tag. |
|
| CPU image tag. Pin e.g. |
|
| CUDA image tag, e.g. |
|
| ML image tag, e.g. |
|
| Host interface the port binds to. |
|
| uid:gid the container runs as. Set to |
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_mcpFor 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 |
| CPU, ~180 MB |
|
| CPU + PyTorch CUDA wheels |
|
| GPU + rembg + |
|
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 --versionDevelopment
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check src tests
ruff format src tests
pytest -qThe 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 |
|
Lint the workflows |
|
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 |
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 toolsLicense
MIT. See LICENSE.
Available Tools
14 toolsimage_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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. | |
| path | No | Path to the image. | |
| model | No | ML model name: u2net, u2netp, isnet-general-use, silueta. | u2net |
| method | No | auto, color or ml. | auto |
| feather | No | Soften the resulting alpha edge by this many pixels. | |
| softness | No | Width of the soft alpha ramp at the edges. | |
| tolerance | No | Colour distance treated as background (color method). | |
| background | No | Composite onto this RGB colour instead of leaving transparency. | |
| base64_data | No | Inline base64 image. | |
| output_name | No | Override the output filename. | |
| return_image | No | Embed the result inline. | |
| output_format | No | Output codec; must support alpha (png, webp, avif, tiff). | png |
| return_base64 | No | Include base64 in the JSON result. | |
| edge_connected | No | Only remove background regions touching the border, protecting interior matches. | |
| strip_metadata | No | Strip metadata from the output. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| glob | No | Glob pattern inside an input root, e.g. 'photos/**/*.jpg'. | |
| limit | No | Maximum files to process in this call. | |
| paths | No | Explicit list of input paths. | |
| quality | No | Lossy quality for every output. | |
| directory | No | Directory (inside an input root) to process recursively. | |
| operations | Yes | Ordered operations applied to every file. | |
| output_format | No | Output codec; defaults to each input's own format. | |
| strip_metadata | No | Strip metadata from every output. | |
| return_first_image | No | Embed only the first output inline, to bound payload size. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| files | No | |
| notes | No | |
| failed | Yes | |
| operation | No | |
| processed | Yes | |
| succeeded | Yes | |
| totalInputBytes | Yes | |
| totalOutputBytes | Yes | |
| inlineImageIncluded | No |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| gpu | No | |
| notes | No | |
| limits | Yes | |
| server | Yes | |
| formats | Yes | |
| backends | Yes | |
| protocol | Yes | |
| security | Yes | |
| operation | No | |
| operations | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | URL for the first image. | |
| path | No | Path to the first image. | |
| align | No | Resize the second image to the first's dimensions before comparing. | |
| base64_data | No | Inline base64 for the first image. | |
| diff_amplify | No | Amplify the diff visually. | |
| compare_to_url | No | URL for the second image. | |
| compare_to_path | No | Path to the second image. | |
| change_threshold | No | Per-pixel difference counted as a change. | |
| compare_to_base64 | No | Inline base64 for the second image. | |
| create_diff_image | No | Also write a visual difference image (white where identical). |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| effort | No | Encoder effort; higher is slower and smaller. | |
| quality | No | Lossy quality 1-100 (higher is better quality and larger). | |
| lossless | No | Prefer lossless compression where supported. | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| output_name | No | Override the output filename (a safe name is derived if omitted). | |
| return_image | No | Embed the resulting image inline so vision-capable clients can see it. | |
| return_base64 | No | Include the base64 payload in the JSON result for clients without file access. | |
| target_format | No | Output codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format. | |
| strip_metadata | No | Remove EXIF/GPS/ICC metadata. Defaults to the server setting (on). | |
| target_size_kb | No | Target maximum file size in kilobytes; quality is searched to fit. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| effort | No | Encoder effort: WebP method 0-6 or AVIF speed (inverted). Higher is slower and smaller. | |
| quality | No | Lossy quality 1-100 (higher is better quality and larger). | |
| keep_icc | No | Keep the ICC colour profile even when other metadata is stripped. | |
| lossless | No | Use the codec's lossless mode where supported. | |
| background | No | RGB colour used to flatten transparency when the target has no alpha channel. | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| output_name | No | Override the output filename (a safe name is derived if omitted). | |
| progressive | No | Write a progressive JPEG. | |
| return_image | No | Embed the resulting image inline so vision-capable clients can see it. | |
| return_base64 | No | Include the base64 payload in the JSON result for clients without file access. | |
| target_format | No | Target codec, e.g. 'webp', 'png', 'avif'. | webp |
| strip_metadata | No | Remove EXIF/GPS/ICC metadata. Defaults to the server setting (on). |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| box | No | Explicit (left, top, right, bottom) box in pixels. | |
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| trim | No | Auto-remove a uniform border instead of using box/aspect_ratio. | |
| clamp | No | Clip a box that falls outside the image instead of failing. | |
| gravity | No | Which part to keep: center, top, bottom-left, ... | center |
| quality | No | Lossy quality 1-100 (higher is better quality and larger). | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| output_name | No | Override the output filename (a safe name is derived if omitted). | |
| aspect_ratio | No | Crop to this width/height ratio, e.g. 1.0 for square, 1.7778 for 16:9. | |
| return_image | No | Embed the resulting image inline so vision-capable clients can see it. | |
| output_format | No | Output codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format. | |
| return_base64 | No | Include the base64 payload in the JSON result for clients without file access. | |
| strip_metadata | No | Remove EXIF/GPS/ICC metadata. Defaults to the server setting (on). | |
| trim_tolerance | No | Colour tolerance for trimming. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| include_exif | No | Include decoded EXIF tags. | |
| include_hashes | No | Include perceptual hashes for duplicate detection. | |
| include_colours | No | Include the dominant colour palette. | |
| max_exif_entries | No | Cap on returned EXIF entries. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| exif | No | |
| image | Yes | |
| notes | No | |
| hashes | No | |
| operation | No | |
| dominantColours | No |
TDQS
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.
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.
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.
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.
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.
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'.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. | |
| path | No | Path to the source image. | |
| widths | No | Target widths, e.g. [480, 960, 1440]. Defaults to a sensible ladder. | |
| quality | No | Starting quality for each variant. | |
| base64_data | No | Inline base64 source image. | |
| output_format | No | Output codec: webp, avif or jpeg. | webp |
| strip_metadata | No | Strip metadata from every variant. | |
| create_placeholder | No | Also produce a tiny blurred placeholder and its base64 data URI. | |
| max_bytes_per_variant | No | Per-variant byte ceiling; quality is searched down per width to fit. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| fit | No | contain (fit inside, keep ratio), cover (fill and crop), fill (stretch), inside (contain but never enlarge), outside (cover without cropping), pad (contain then pad) | contain |
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| width | No | Target width in pixels. | |
| filter | No | nearest, box, bilinear, hamming, bicubic, lanczos, or auto. | |
| height | No | Target height in pixels. | |
| gravity | No | Anchor for cover cropping or padding, e.g. center, top, bottom-right. | center |
| percent | No | Scale by this percentage of the original. | |
| quality | No | Lossy quality 1-100 (higher is better quality and larger). | |
| background | No | RGBA fill used by pad and rotate. | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| only_shrink | No | Never enlarge; leave smaller images untouched. | |
| output_name | No | Override the output filename (a safe name is derived if omitted). | |
| return_image | No | Embed the resulting image inline so vision-capable clients can see it. | |
| output_format | No | Output codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format. | |
| return_base64 | No | Include the base64 payload in the JSON result for clients without file access. | |
| strip_metadata | No | Remove EXIF/GPS/ICC metadata. Defaults to the server setting (on). |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| flip | No | Mirror the image: horizontal, vertical or both. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| angle | No | Degrees counter-clockwise. | |
| expand | No | Grow the canvas so rotation does not clip corners. | |
| quality | No | Lossy quality 1-100 (higher is better quality and larger). | |
| background | No | RGBA fill for exposed areas. | |
| auto_orient | No | Apply the EXIF orientation tag first. | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| output_name | No | Override the output filename (a safe name is derived if omitted). | |
| return_image | No | Embed the resulting image inline so vision-capable clients can see it. | |
| output_format | No | Output codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format. | |
| return_base64 | No | Include the base64 payload in the JSON result for clients without file access. | |
| strip_metadata | No | Remove EXIF/GPS/ICC metadata. Defaults to the server setting (on). |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. Disabled unless PICTOR_ALLOW_NET_FETCH=true. | |
| path | No | Path to the image, relative to an allowed input root (or absolute inside one). | |
| size | No | Exact (width, height) of the thumbnail. | |
| method | No | attention (edge energy), entropy (local detail) or center. | attention |
| quality | No | Lossy quality 1-100 (higher is better quality and larger). | |
| sharpen | No | Unsharp-mask amount applied after resizing; 0 disables. | |
| base64_data | No | Inline image bytes as base64, optionally a data: URI. | |
| output_name | No | Override the output filename (a safe name is derived if omitted). | |
| return_image | No | Embed the resulting image inline so vision-capable clients can see it. | |
| max_dimension | No | Shorter alternative: fit within a square of this size, keeping ratio. | |
| output_format | No | Output codec: jpeg, png, webp, avif, tiff, gif, bmp, ico, jpeg2000, qoi, ppm. Defaults to the input format. | |
| return_base64 | No | Include the base64 payload in the JSON result for clients without file access. | |
| strip_metadata | No | Remove EXIF/GPS/ICC metadata. Defaults to the server setting (on). |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch (disabled by default). | |
| path | No | Path to the image, inside an allowed input root. | |
| quality | No | Lossy quality for the final encode. | |
| lossless | No | Use a lossless encode where supported. | |
| operations | Yes | Ordered list of operations, each with an 'op' field. | |
| base64_data | No | Inline base64 image, optionally a data: URI. | |
| output_name | No | Override the output filename. | |
| return_image | No | Embed the result inline for vision clients. | |
| output_format | No | Output codec; defaults to the input format. | |
| return_base64 | No | Include base64 in the JSON result. | |
| strip_metadata | No | Strip EXIF/GPS metadata from the output. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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'.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | http(s) URL to fetch. | |
| path | No | Path to the base image. | |
| text | No | Watermark text. | |
| tile | No | Repeat the watermark across the whole image. | |
| scale | No | Logo width as a fraction of the image's shorter edge. | |
| colour | No | Text RGB colour. | |
| shadow | No | Draw a drop shadow behind text for legibility. | |
| opacity | No | Watermark opacity. | |
| padding | No | Distance from the edge in pixels. | |
| quality | No | Lossy quality. | |
| spacing | No | Gap between tiles in pixels. | |
| position | No | center, top, bottom, left, right, or a corner like bottom-right. | bottom-right |
| rotation | No | Rotate the watermark, in degrees. | |
| font_size | No | Text size in pixels; defaults to a fraction of the image. | |
| base64_data | No | Inline base64 base image. | |
| font_family | No | Font name from the server's font index, e.g. DejaVuSans-Bold. | |
| output_name | No | Override the output filename. | |
| return_image | No | Embed the result inline. | |
| stroke_width | No | Outline width for text. | |
| output_format | No | Output codec; defaults to the input format. | |
| return_base64 | No | Include base64 in the JSON result. | |
| strip_metadata | No | Strip metadata from the output. | |
| watermark_path | No | Path to a logo/watermark image (mutually exclusive with text). |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| input | No | Summary of the source image, when one was read. |
| notes | No | |
| steps | No | |
| metrics | No | Operation-specific measurements (e.g. comparison scores). |
| outputs | No | |
| operation | Yes | Tool that produced this result. |
| sizeChange | No | |
| inlineImageIncluded | No | True when an image block accompanies this result for vision-capable clients. |
TDQS
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.
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.
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.
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.
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.
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.
14 tool updates
v1.0.0- First observed
image_background_remove - First observed
image_batch - First observed
image_capabilities - First observed
image_compare - First observed
image_compress - First observed
image_convert - First observed
image_crop - First observed
image_info - First observed
image_optimize_web - First observed
image_resize - First observed
image_rotate - First observed
image_thumbnail - First observed
image_transform - First observed
image_watermark
TDQS
Scored across 14 tools
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.
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.
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.
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
Related MCP Connectors
Image processing over MCP: compress and split images, generate images and QR codes.
Convert images to PNG, JPEG, WebP, or AVIF through one public remote MCP tool.
AI-powered image processing via GPU. Remove backgrounds and upscale images (2x/4x) directly from any MCP client. OAuth 2.1 authenticated, returns processed images inline with download links. Free credits on signup at maskr.io.
Edit images over MCP with object removal, background removal, and guided generative edits.
Related MCP Servers
- AlicenseAqualityCmaintenanceLocal image processing MCP server providing background removal, stock media search, resize, format conversion, and collage creation.5MIT
- AlicenseAqualityAmaintenanceMCP server for raster image manipulation using Pillow and OpenCV. Enables image operations such as resizing, cropping, rotating, format conversion, filtering, and auto-enhancement via MCP tools.8MIT
- AlicenseNot gradedqualityBmaintenanceProvides image understanding capabilities via local OCR and cloud VLM, enabling text extraction and visual description through MCP.11 npm2MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI image processing tools such as vectorization, background removal, upscaling, and logo generation from any MCP-compatible client like Claude Code or Cursor.13 npmMIT