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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.5/5.0
Behavior3/5

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

No annotations are provided, so the description carries the full behavioral burden. "Report" implies a read-only, non-destructive introspection call, and the enumerated fields effectively describe what is returned, but it does not explicitly state that nothing is mutated or reveal any side effects or cost.

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

Conciseness5/5

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

A single efficient sentence, front-loaded with the verb and listing the useful outputs. Every clause earns its place with no filler.

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

Completeness4/5

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

For a simple parameterless diagnostic tool with no output schema, naming the four reported values in the description adequately substitutes for a return-value spec. Minor gap: no indication of format or whether results are environment-dependent.

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

Parameters4/5

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

The tool takes zero parameters, so no parameter semantics are required; the empty schema is self-explanatory. Baseline for a parameterless tool applies.

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

Purpose4/5

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

The description states a specific verb ("Report") and enumerates the exact resources reported: torch version, compute device, roots, and models folders. It is clearly distinguishable from siblings like list_models or upscale as a diagnostic/introspection tool, though it does not explicitly name or contrast with those siblings.

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

Usage Guidelines2/5

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

There is no guidance on when to call this tool versus alternatives, no prerequisites, and no exclusions. Usage is only implied by the diagnostic nature of the reported fields, leaving the agent to infer context.

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

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.
ParametersJSON Schema
NameRequiredDescriptionDefault
jobYesJob id to cancel.

TDQS

A4.3/5.0
Behavior4/5

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

With no annotations, the description carries the full burden and discloses key behaviors: a running job stops at the next tile boundary, the returned status may still be 'running', and a cancelled job writes no output. It does not cover permissions or error cases, but the core cancellation semantics are well explained.

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

Conciseness5/5

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

Three short sentences with no waste. The core action is front-loaded, followed by the tile-boundary behavior and the output consequence. Every sentence earns its place.

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

Completeness5/5

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

For a one-parameter mutation tool with no output schema or annotations, the description is complete enough. It explains the cancellation effect, the status caveat, and the lack of output, so an agent has what it needs to invoke and interpret the call.

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

Parameters3/5

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

Schema description coverage is 100%, and the schema already defines the single 'job' parameter as 'Job id to cancel.' The description adds no additional parameter syntax or format details, so the baseline of 3 is appropriate.

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

Purpose5/5

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

States a specific verb and resource: 'Cancel a queued job, or stop a running one.' It also clarifies the queued vs running distinction and references the job_status sibling for follow-up, so an agent can distinguish it from status/list tools.

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

Usage Guidelines4/5

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

Provides clear context for use: cancel a queued job or stop a running one, and check job_status afterward. It does not name explicit exclusions or alternatives, but no sibling offers cancellation, so the context is sufficient.

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

jobs_listA

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

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.7/5.0
Behavior3/5

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

No annotations are provided, so the description carries the full behavioral burden. It does disclose meaningful behavior beyond the name: the returned set includes queued, running, and only the last finished jobs, and results are ordered newest first. It omits pagination, result-count limits, and what 'recent' bounds actually are.

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

Conciseness5/5

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

A single front-loaded sentence that states the action, the returned set, and the sort order with no wasted words.

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

Completeness4/5

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

For a no-parameter, no-annotation read tool with no output schema, the description covers what the tool does and what the result set contains. The remaining gap is the absence of any bound on 'recent' (count or pagination), which matters because there is no output schema to fall back on.

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

Parameters4/5

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

The tool takes zero parameters, so per the rubric the baseline is 4. There is nothing for the description to clarify beyond what the empty schema already communicates.

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

Purpose4/5

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

States a specific verb (List) and resource (jobs) with scope ('recent jobs: queued, running and the last finished ones') and ordering ('newest first'). However, it never distinguishes itself from the sibling job_status or jobs_cancel, so an agent must infer that this is the aggregate-list view rather than a single-job lookup.

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

Usage Guidelines3/5

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

Usage is implied by the scope description — an agent can infer this is for browsing/overview rather than per-job inspection. But no explicit when-to-use or when-not-to-use guidance is given, and neither job_status (single job) nor jobs_cancel is named as the alternative for those cases.

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

job_statusB

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

ParametersJSON Schema
NameRequiredDescriptionDefault
jobYesJob id returned by upscale, e.g. 'j3'.
wait_secondsNoSeconds to wait for the job to finish before answering; 0 = answer now.

TDQS

B3.3/5.0
Behavior3/5

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

With no annotations, the description carries the full burden. It does disclose a useful lifecycle behavior — the result is only available once the job is done — but says nothing about whether the call is read-only, what happens for an unknown/expired job id, or any rate/blocking characteristics beyond what the schema states.

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

Conciseness5/5

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

A single tight sentence that front-loads the core return content and adds the conditional result clause with zero waste.

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

Completeness3/5

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

No output schema and no annotations, so the description is the only source of behavioral context. It conveys what comes back and that the result is conditional on completion, but omits error/expiry behavior and polling guidance for a tool likely used in a wait loop.

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

Parameters3/5

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

Schema description coverage is 100%, so both the required job id and wait_seconds are already fully documented in the schema. The description adds no syntax, format, or interpretation details beyond that, so the baseline 3 applies.

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

Purpose4/5

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

Clear specific verb ("Report") plus resource (one job's status, progress, result) and an explicit scope of "one job", which implicitly separates it from jobs_list. However, it never names an actual sibling as the alternative, so the differentiation is left to inference.

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

Usage Guidelines2/5

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

No when-to-use guidance, no mention of jobs_list or jobs_cancel, and nothing about when polling versus using wait_seconds is appropriate. Usage is only inferable from the name.

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

list_modelsC

List the model files in the models folders.

ParametersJSON Schema
NameRequiredDescriptionDefault
inspectNoLoad each file on the CPU to report its architecture, scale and channels. The first listing of a large collection can take a few seconds; results are cached until a file changes.

TDQS

C2.9/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden, yet it discloses nothing beyond the bare action. Notably, the schema documents the costly CPU inspect/loading behavior and caching, but the description itself never mentions performance impact, caching, or that this is a read-only operation.

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

Conciseness4/5

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

A single front-loaded sentence with zero wasted words. It is efficient, though its brevity reflects under-specification rather than tight editing of rich content.

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

Completeness3/5

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

For a no-argument, read-only listing tool with a fully documented single parameter and no output schema, the description is minimally adequate. However, it omits any indication of what the listing returns or how models folders are resolved, leaving gaps an agent must guess at.

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

Parameters3/5

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

Schema description coverage is 100% and the single parameter (inspect) is well documented in the schema with its default, cost, and caching semantics. The description adds no parameter meaning beyond the schema, so the baseline of 3 applies.

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

Purpose4/5

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

States a specific verb (list) and resource (model files in the models folders), which is clearly distinguishable from the job/device siblings such as jobs_list and device_info. It stops short of explicitly naming the sibling it differs from, so it is clear but not maximally differentiated.

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

Usage Guidelines2/5

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

The description gives no guidance on when to use this versus alternatives, nor any prerequisites or context about what the models folders are. The intended use (enumerate available models before an upscale/inference call) must be inferred from the sibling set.

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

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.
ParametersJSON Schema
NameRequiredDescriptionDefault
halfNoRun in float16 on the GPU (faster, less memory) if the model supports it.
inputYesInput image: absolute path inside a read root, or relative to the first read root. Anything Pillow reads, plus HEIC/HEIF/AVIF and 16-bit PNG/TIFF (kept at 16 bits).
modelYesModel file name or stem from list_models, e.g. 'RealESRGAN_x4plus'.
outputNoOutput path (absolute inside the write root, or relative to it). The suffix picks the format: .png .jpg .tif .webp; .png and .tif keep 16 bits for a deeper source. Default: <input stem>.<model stem>.<scale>.png in the write root.
overwriteNoReplace an existing output file.
tile_sizeNoTile edge in input pixels; 0 = whole image in one pass.
jpeg_qualityNoQuality for .jpg output.
output_scaleNoFinal size relative to the INPUT. Omit to keep the model's native scale (4x for a 4x model). 1 = resize back to the input size (for compositing over the original). Other values resize to that multiple with LANCZOS.
tile_overlapNoContext margin around each tile, in input pixels, to hide seams.
wait_secondsNoSeconds to wait for the job to finish before returning. Omit to wait until it is done. 0 returns the job id at once. A client that times out calls after about a minute should pass about 45 and poll job_status while the status is queued or running.

TDQS

A3.5/5.0
Behavior3/5

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

With no annotations, the description carries the full burden. It usefully discloses that the call writes under the write root and returns a job status rather than a finished result, plus what `result` contains when done. However, it omits failure behavior, whether the operation is always async, and any auth/permission or resource constraints, so disclosure is partial.

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

Conciseness4/5

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

Three short sentences with the core action front-loaded and no filler. The trailing sentence about `result` contents earns its place given there is no output schema, though the description is terse enough that nothing needed trimming.

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

Completeness4/5

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

For a 10-parameter mutation-style tool with no annotations and no output schema, the description supplies the essential missing piece: the shape of the returned job status and `result` fields. It stops short of covering error cases or the fact that the caller may need to poll, which would make it fully self-contained.

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

Parameters3/5

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

Schema description coverage is 100%, so all ten parameters are already well documented in the schema. The description adds no parameter-level syntax, defaults, or constraints beyond what the schema provides, so the baseline 3 applies.

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

Purpose4/5

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

The description names a specific verb+resource+scope: 'Upscale (or restore) one image with one model, writing the result under the write root.' This is far more specific than a tautology and makes the single-image/single-model granularity clear. It does not explicitly differentiate itself from siblings like job_status or list_models, so it stops short of a 5.

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

Usage Guidelines3/5

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

Usage is only implied: 'one image with one model' hints that batching requires repeated calls, and the mention of returning job status implies the polling workflow, but no explicit when-to-use/when-not or alternative tools are named. The polling hint lives in the wait_seconds schema description rather than the tool description.

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

Tool Schema Changelog

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

  1. 6 tool updatesv0.3.0
    • First observeddevice_info
    • First observedjob_status
    • First observedjobs_cancel
    • First observedjobs_list
    • First observedlist_models
    • First observedupscale

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 issues

Related MCP Connectors

Related MCP Servers