upscale-mcp
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., "@upscale-mcpupscale ~/Pictures/portrait.jpg with Real-ESRGAN x4plus to original size"
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.
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 syncRelated 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 4fa0d38905f75ac06eb49a7951b426670021be3018265fd191d2125df9d682f1It 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 --checkIt 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 fromlist_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 unlessoverwriteis true.output_scale: omit for the model's native scale,1for 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;0to 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 underresultonce it isdone.
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 pytestThe 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 toolsdevice_infoA
Report the torch version, the compute device in use, the roots and the models folders.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| job | Yes | Job id to cancel. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| job | Yes | Job id returned by upscale, e.g. 'j3'. | |
| wait_seconds | No | Seconds to wait for the job to finish before answering; 0 = answer now. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| inspect | No | Load 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
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| half | No | Run in float16 on the GPU (faster, less memory) if the model supports it. | |
| input | Yes | Input 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). | |
| model | Yes | Model file name or stem from list_models, e.g. 'RealESRGAN_x4plus'. | |
| output | No | Output 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. | |
| overwrite | No | Replace an existing output file. | |
| tile_size | No | Tile edge in input pixels; 0 = whole image in one pass. | |
| jpeg_quality | No | Quality for .jpg output. | |
| output_scale | No | Final 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_overlap | No | Context margin around each tile, in input pixels, to hide seams. | |
| wait_seconds | No | Seconds 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
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.
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.
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.
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.
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.
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.
6 tool updates
v0.3.0- First observed
device_info - First observed
job_status - First observed
jobs_cancel - First observed
jobs_list - First observed
list_models - First observed
upscale
TDQS
Scored across 6 tools
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.
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.
Six tools is well-scoped for an image upscaling server, with each tool earning its place across environment, models, execution, and job control.
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
Related MCP Connectors
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.
Chat, image, video, and audio models from many vendors behind one remote MCP server and one key.
Remote MCP for RunComfy: ComfyUI deployments, hosted models, LoRA training. 31 tools.
Hosted MCP server for AI photo editing: nothing to install, no desktop photo editor needed.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceMCP server that enables local Apple on-device Foundation Model access via any MCP client, supporting text generation, structured output, and multi-turn chat on macOS.2MIT
- AlicenseAqualityBmaintenanceWraps ComfyUI to generate images, video, and upscales from text prompts via MCP tools, providing a curated workflow interface without the node editor.84MIT
- AlicenseNot gradedqualityBmaintenanceEnables private, GPU-backed image upscaling via a local Hermes MCP bridge that submits jobs to an authenticated Upscayl worker API.MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to generate images using local ComfyUI with FLUX.2 Klein models optimized for Apple Silicon, plus model management and system diagnostics.1MIT