Skip to main content
Glama

upscale-mcp

This is an MCP server I (well, Claude, actually) wrote for my personal use on my MacBook Pro M5 Pro, but there's no reason not to make it public.

I release it under the Apache 2.0 license.

All copyrightable files in this project copyright 2026 Sidney Markowitz

The MCP server runs open-weight image upscaling and restoration models locally, on the Mac's GPU through PyTorch's Metal (MPS) backend. Models are loaded with spandrel, the loader chaiNNer uses, so the usual community model files work.

It is meant to be driven by an MCP client such as Claude Desktop: list the installed models, run one over an image, get the result written to a folder. With output_scale=1 the image is upscaled and then resized back to its original size, so the result lines up pixel for pixel with the original and can be composited over it (for example masked to one subject in an editor).

AI upscalers invent plausible detail. They do not recover detail the camera did not capture, and different models invent very differently, so it pays to compare a few on the image at hand.

Install

Requires uv. .python-version pins Python 3.12, which uv installs if needed.

cd ~/OpenSource/upscale-mcp
uv sync

Related MCP server: comfyops-mcp

Models

Model files live outside the repo. By default the server looks in ~/.local/share/upscale-mcp/models; pass --models-dir (repeatable) to use other folders, for example ComfyUI's models/upscale_models. Folders are searched recursively. Supported file types: .pth, .pt, .safetensors, .ckpt. Pickled .pth/.pt/.ckpt files are loaded by spandrel with torch's restricted weights_only unpickler.

A reasonable baseline is Real-ESRGAN x4plus (BSD-3-Clause):

https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth
sha256 4fa0d38905f75ac06eb49a7951b426670021be3018265fd191d2125df9d682f1

It is trained to remove heavy degradation and tends to smooth fine texture such as fur into a painted look. OpenModelDB catalogues many alternatives, filterable by purpose (photo, JPEG, blur, ...). Some architectures that spandrel supports only through the separate spandrel_extra_arches package (non-commercial licences) are not included.

First run

Before adding the server to Claude Desktop, run the self-test in Terminal:

uv run upscale-mcp --check

It prints the torch version and device, lists the models it finds, then runs the first model (or --check-model NAME) on a small synthetic image on the GPU and on the CPU and reports the largest difference between the two. A few levels (out of 255) is floating-point noise; a large difference means the Metal backend is computing something different from the CPU.

Claude Desktop

Add to mcpServers in ~/Library/Application Support/Claude/claude_desktop_config.json:

"upscale": {
  "command": "/Users/sidney/.local/bin/uv",
  "args": [
    "--directory", "/Users/sidney/OpenSource/upscale-mcp",
    "run", "upscale-mcp",
    "--models-dir", "/Users/sidney/.local/share/upscale-mcp/models",
    "--read-root", "/Users/sidney/Pictures",
    "--write-root", "/Users/sidney/Pictures/Photocraft"
  ]
}

Tools

  • device_info: torch version, device in use, roots, models folders.

  • list_models: model files with architecture, purpose, scale and whether they support half precision.

  • upscale: run one model over one image, as a background job.

    • input: inside a read root; relative paths start at the first read root.

    • model: file name or stem from list_models.

    • output: inside the write root (relative paths start there); the suffix picks the format (.png, .jpg, .tif, .webp). Default <input stem>.<model stem>.<scale>.png. Existing files are not replaced unless overwrite is true.

    • output_scale: omit for the model's native scale, 1 for the original size, or any other multiple of the input size (LANCZOS resize).

    • tile_size (default 512, 0 = one pass), tile_overlap (default 32), half (float16 on the GPU), jpeg_quality.

    • wait_seconds: omit to wait until the job is done and get the result from the same call; 0 to get the job id back at once; anything else to wait up to that long. The reply is the job's status, with the result under result once it is done.

  • job_status: one job's status, tile progress and, when done, its result. wait_seconds (default 0) waits up to that long for it to finish first.

  • jobs_list: recent jobs, newest first.

  • jobs_cancel: cancel a queued job, or stop a running one after its current tile. A cancelled job writes no output.

Jobs run one at a time on the GPU. Some clients give up on a call long before a slow model finishes (a Claude cloud session reaching the Mac through the desktop app waits 60 seconds), so such a client should pass wait_seconds of about 45 and keep calling job_status while the status is queued or running. A job shows 0 of 0 tiles while its model is loading.

Outputs are written to a hidden temporary file and renamed into place, so a file that exists under its final name is always complete.

Inputs can be anything Pillow reads, plus HEIC/HEIF/AVIF (through pillow-heif) and 16-bit PNG and TIFF. Images deeper than 8 bits, such as a phone's 10-bit HEIC, are processed at 16 bits throughout and written as 16-bit PNG or TIFF. JPEG and WebP output is always 8-bit, with a warning if that rounds a deeper result. The reply reports input_bits and output_bits.

The EXIF orientation is applied to the pixels and the ICC profile is copied to the output. Alpha is carried through with a plain LANCZOS resize, not the model.

Paths

Nothing is readable or writable unless granted at startup with --read-root (repeatable) and --write-root. Paths are resolved through symlinks before the check, so a symlink inside a root that points outside it is refused. Missing folders under the write root are created.

Tiling

Large images are processed in tiles so GPU memory depends on the tile size, not the image size. Each tile is run with tile_overlap pixels of extra context on every side and only its centre is kept. Measured with Real-ESRGAN x4plus on a 256x256 crop, 128-pixel tiles against a single pass: overlap 16 left 1.2% of pixels more than 2 levels off (max 9); overlap 32 left 0.065% (max 8), which is not visible.

Tests

uv run pytest

The tests need no model weights or GPU: they check that tiled and single-pass output are identical for a model with a known receptive field, the padding for architectures with size requirements, progress reporting, path confinement, model-name resolution, the job queue (one at a time, cancelling queued and running jobs, refusing two jobs on one output), atomic saves, and the 16-bit path (10-bit HEIF scaling, EXIF orientation, 16-bit PNG and TIFF round trips with their ICC profiles).

Available Tools

6 tools
device_infoA

Report the torch version, the compute device in use, the roots and the models folders.

Inputs: no named parameters.

jobs_cancelA

Cancel a queued job, or stop a running one after the tile it is working on.

A running job stops at the next tile boundary, so the returned status may
still be "running"; check job_status. A cancelled job writes no output.

Inputs: job (required).

jobs_listA

List recent jobs (queued, running and the last finished ones), newest first.

Inputs: no named parameters.

job_statusB

Report one job's status, progress and, once done, its result.

Inputs: job (required), wait_seconds.

list_modelsC

List the model files in the models folders.

Inputs: inspect.

upscaleA

Upscale (or restore) one image with one model, writing the result under the write root.

Returns the job's status. When the job is done, `result` holds the output
path, sizes, model details, device and timing.

Inputs: input (required), model (required), half, output, +6 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.5/5.0

    Scored across 6 tools

    Disambiguation4/5

    Each tool targets a clear resource+action: environment inspection, model listing, running upscales, and job management. Minor overlap exists because device_info reports 'roots and models folders' while list_models lists model files, which could slightly blur the informational pair, but boundaries remain understandable.

    Naming Consistency3/5

    Conventions are mixed: list_models is verb_noun, upscale is a bare verb, job_status is singular noun_noun while jobs_list and jobs_cancel use plural noun_verb. It stays readable but is not a single predictable pattern.

    Tool Count5/5

    Six tools is well-scoped for an image upscaling server, with each tool earning its place across environment, models, execution, and job control.

    Completeness4/5

    The server covers the core lifecycle: discover device/models, launch an upscale, poll status, list jobs, and cancel jobs. A batch/multi-image upscale entry point is a minor gap given upscale handles one image at a time.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers