pixel-mcp
Allows exporting pixel art documents to Aseprite JSON format for use in Aseprite editor.
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., "@pixel-mcp draw a 16x16 pixel art of a star"
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.
pixel-mcp
An MCP server that lets an LLM create and edit pixel art reliably. Implements SPEC.md: indexed-color documents (8x8 to 256x256), layers, frames, symmetry, grid-text editing, server-side PNG/GIF rendering, reference quantization, and advisory quality linting.
Plain JavaScript with TypeScript-checked JSDoc types (tsc --checkJs, strict).
No native dependencies.
Setup
pnpm install
pnpm check # typecheck (tsc, strict, noEmit)
pnpm test # node:test suite, includes an in-memory MCP end-to-end test
pnpm start # run the server on stdioClaude Desktop / Claude Code style config:
{
"mcpServers": {
"pixel": {
"command": "node",
"args": ["c:/path/to/pixel-mcp/src/index.js"]
}
}
}Related MCP server: kayamcp
Persistence
Documents survive restarts. Everything is written through to disk on every successful edit, and loaded on boot:
<dataDir>/
documents/<docId>/
document.json # size, palette, layers, frames, symmetry, tile mode, stamps, selections
cels/<frame>__<layer>.bin # raw palette-index buffers
preview.png # composited frame 0, human-viewable
refs/<refId>.png # loaded reference imagesdataDir defaults to the platform data directory (%LOCALAPPDATA%\pixel-mcp
on Windows, ~/Library/Application Support/pixel-mcp on macOS,
$XDG_DATA_HOME/pixel-mcp elsewhere). Override with --data-dir <path> or
PIXEL_MCP_DATA_DIR. Undo/redo stacks and checkpoints are in-memory only,
like a normal editor.
PIXEL_MCP_EXPORT_SCALE sets the default export scale factor for the project
(built-in 4). Set it to 1 for native-resolution delivery — game textures an
engine samples want no upscaling — so the model needn't pass scale on every
export. A per-call scale argument still overrides it.
Tool surface (~60 tools)
Every tool that ingests a PNG accepts exactly one of png_base64 or path.
Paths are read from the server filesystem; relative paths resolve against the
server process's working directory. Base64 remains useful for remote clients.
Lifecycle —
create_document,open_document(PNG import + quantize),export(png / gif / aseprite_json — writes a real file to a caller path or<dataDir>/exports/and returns the path;return_base64for the bytes; default scale fromPIXEL_MCP_EXPORT_SCALE),split_sprite_sheet(regular PNG grid from base64 or a server-local path to separate documents),pack_sprite_sheet(document frames to a transparent PNG sheet with layout metadata),resize_canvas,undo,redo,describe,list_documents,delete_document,set_symmetry,set_tile_mode,set_active_layer,set_active_frame,checkpointPalette —
set_palette_color,add_palette_color,remove_palette_color(remaps pixels),swap_palette_indices,generate_ramp(cool shadows / warm highlights),replace_color,mix_palette_colors(blend two entries into a third),adjust_palette_color(HSL nudge in place or as a new variant),extract_palette(median-cut a PNG into hex colors + a swatch, no document — feedscreate_document)Drawing —
set_pixels,line(pixel-perfectno_doubles),rect,ellipse,flood_fill,outline,paste_gridShading & texture —
shade(ramp-step or nearest-snap darken/lighten),dither_gradient(ordered-dither blend between two indices),scatter(seeded grain/noise from an index set),import_image(drop an external PNG onto a layer or store it as a stamp)Regions —
copy_region/cut_region(named stamps),paste_stamp(flip/rotate; ignores symmetry by default so placed content is never silently mirror-corrupted),move_region,mirror_region,shift_layer,clear_region,save_selectionLayers —
add_layer,remove_layer,rename_layer,reorder_layer,set_layer_visibility,set_layer_lock,merge_downViewing —
view,view_text,view_diff(vs. checkpoint),view_window(zoomed patch + context thumbnail),view_tiled(NxN repeat with seams marked — the perception tool for seamless textures)References —
load_reference,quantize_reference,view_referenceAnimation —
add_frame,remove_frame,reorder_frame,set_frame_duration,copy_cel,view_frames(onion skin; the model's perception tool for motion),preview_gif(human-facing output — clients show a GIF as a single still in model context)Lint —
lintwith all ten spec rules (orphans, doubles, banding, pillow_shading, unused_colors, near_duplicates, broken_outline, symmetry_drift, stray_alpha, tile_seams) plus persistent waivers vialint_waive/lint_unwaive: judged-and-accepted findings stay suppressed on every later pass.doublesflags rhythm breaks in diagonal staircases, not every 2-over-1 step, so ellipses don't light up;symmetry_driftreports one finding per contiguous asymmetric region;tile_seamsonly fires when a tile mode is set, so ordinary sprites stay quiet.
Every editing tool returns the changed bounding box plus a render of the
changed region (4px context) and a whole-canvas thumbnail; pass
render: false to skip. All edits are atomic — a failed call leaves the
document untouched — and undoable (depth 100).
Grid text
One character per pixel, one line per row: . = transparent, 1-9 map to
palette indices 1-9, a-z to 10-35, A-Z to 36-61, # = 62, @ = 63.
(0 parses as transparent.) Digit characters equal their palette index,
matching the spec's example; the spec's prose lists one more character than
there are slots, which this resolves.
grid 8x8 palette=4
.111111.
11111111
11211211
11111111
12111121
11222211
11111111
.111111.
legend: 1=yellow #f8e030 2=black #202020Workflow tip that earned its place in live testing: for a planned sequence of
edits, pass render: false on each call and batch-verify with view at
milestones. Use view_text for pixel-exact ground truth and view for
gestalt — the two channels together are the point.
Spec deviations and gap-fills
set_symmetry,set_active_frame,save_selection,list_documents,delete_documentare added; the spec references the underlying state but defines no tool to manage it.open_documentandload_referenceaccept PNG only; Aseprite binary import is not implemented (export to Aseprite JSON is).quantize_referencewithpalette: 'auto'appends the extracted colors to the document palette so the returned indices are immediately valid.Persistence (not in the spec) as described above.
Development
Manual end-to-end smoke that writes renders to scripts/smoke-out/ for
eyeballing: node scripts/smoke.mjs.
Available Tools
70 toolsadd_frameA
Append a frame (optionally duplicating an existing one). The new frame becomes active.
Inputs: doc_id (required), duration_ms, duplicate_from.
add_layerB
Add a new transparent layer (becomes the active layer).
Inputs: name (required), doc_id (required), index.
add_palette_colorB
Append a color to the palette. Fails when the palette is full (64 entries).
Inputs: hex (required), doc_id (required), name.
adjust_palette_colorA
Nudge a palette color in HSL space: darken/lighten via lightness, shift saturation or hue. Saves the model from hand-computing hex. Edits in place (all pixels using it update), or appends a fresh variant with as_new.
Inputs: index (required), doc_id (required), hue, name, +4 more.
checkpointB
Label the current state for later comparison with view_diff.
Inputs: label (required), doc_id (required).
clear_regionB
Set every pixel in a region to transparent.
Inputs: doc_id (required), region (required), clip, frame, +3 more.
copy_celC
Copy one layer's pixels from one frame to another.
Inputs: doc_id (required), to_frame (required), from_frame (required), layer.
copy_from_documentA
Copy a region of pixels from another document into this one, lossless in palette-index space — the assembly tool for multi-agent fan-out: subagents draw parts in their own documents, then a parent composes them. Source colors map to identical destination palette entries where they exist; the rest are appended ('extend', default) or snapped to the perceptually nearest existing entry ('nearest'). Placed content: ignores symmetry by default, like paste_stamp.
Inputs: doc_id (required), from_doc_id (required), x, y, +10 more.
copy_regionB
Copy a region into a named stamp for later paste_stamp.
Inputs: name (required), doc_id (required), region (required), frame, +1 more.
create_documentA
Create a new pixel art document. Palette is a preset name (gameboy, grayscale4, pico8, sweetie16, nes) or a list of hex colors; index 0 is always transparent.
Inputs: width (required), height (required), palette.
cut_regionB
Copy a region into a named stamp and clear it from the layer.
Inputs: name (required), doc_id (required), region (required), clip, +4 more.
delete_documentA
Permanently delete a document from memory and disk. Cannot be undone.
Inputs: doc_id (required).
describeB
Full document state: size, palette with usage counts, layers, frames, symmetry, stamps, selections.
Inputs: doc_id (required).
dither_gradientA
Fill a region with an ordered-dither gradient between two palette colors. The indexed-color way to blend a smooth-looking transition: from_index at one end, to_index at the other. axis horizontal/vertical/radial; pattern bayer (4x4, smoother) or checker (2x2, coarser). Either index may be 0 to fade to/from transparent.
Inputs: doc_id (required), region (required), to_index (required), from_index (required), +7 more.
ellipseC
Draw an ellipse centered at (cx,cy) with radii rx,ry (outline or filled).
Inputs: cx (required), cy (required), rx (required), ry (required), +8 more.
exportA
Export the document to a real file and return its absolute path. png: one frame with true transparency. gif: all frames animated. aseprite_json: JSON with embedded spritesheet. This is delivery for humans — the response contains no rendered image; use view/view_frames for perception.
Inputs: doc_id (required), format (required), loop, path, +3 more.
extract_paletteA
Extract a color palette from a PNG supplied as exactly one of png_base64 or path, with median-cut quantization, WITHOUT creating a document. Returns a list of hex colors ready to pass to create_document(palette=[...]) (or add_palette_color), plus a swatch render so the colors are visible. Use this to pull a palette from a reference image; use open_document when you also want its pixels.
Inputs: path, sort, max_colors, png_base64.
flood_fillA
Flood fill from a seed pixel. contiguous=false recolors every pixel that matches the seed color.
Inputs: x (required), y (required), index (required), doc_id (required), +6 more.
generate_rampB
Append a shading ramp built around a base color (dark to light). Shadows hue-shift toward cool, highlights toward warm.
Inputs: steps (required), doc_id (required), base_hex (required), name, +1 more.
import_imageA
Import an external PNG supplied as exactly one of png_base64 or path onto the canvas. Resamples to width x height (default native size), maps to the document palette ('document') or extends the palette with median-cut colors ('extend'), then writes it to a layer at (x,y). With as_stamp it is stored as a named stamp instead (place later with paste_stamp). Unlike open_document (new doc) this drops pixels into the current canvas.
Inputs: doc_id (required), x, y, clip, +12 more.
lineB
Draw a Bresenham line. no_doubles removes L-shaped double pixels for pixel-perfect diagonals.
Inputs: x1 (required), x2 (required), y1 (required), y2 (required), +8 more.
lintA
Run pixel-art quality checks and return findings with a render circling them. Rules: orphans, doubles, banding, pillow_shading, unused_colors, near_duplicates, broken_outline, symmetry_drift, stray_alpha, tile_seams. Findings are advisory; nothing is auto-fixed.
Inputs: doc_id (required), frame, rules, region.
lint_unwaiveA
Remove a lint waiver by id, or all waivers for the document when no id is given.
Inputs: doc_id (required), waiver_id.
lint_waiveA
Persistently waive lint findings so they stay waived on every future pass (reported only as a count). Scope to a region to waive only there; omit the region to waive the whole rule.
Inputs: rule (required), doc_id (required), note, region.
list_documentsA
List all documents, including ones persisted by other sessions sharing the data dir.
Inputs: no named parameters.
load_referenceA
Store a reference PNG supplied as exactly one of png_base64 or path under an id for quantizing or side-by-side viewing.
Inputs: ref_id (required), path, png_base64.
merge_downA
Merge a layer into the one below it. Non-transparent top pixels replace bottom pixels; partial layer opacity is discarded.
Inputs: doc_id (required), layer.
mirror_regionB
Mirror a region in place across its own bounding-box axis.
Inputs: axis (required), doc_id (required), region (required), clip, +4 more.
mix_palette_colorsA
Blend two palette colors and store the result. The indexed-color way to make an in-between shade: t interpolates from color a (0) to color b (1). Appends a new entry by default, or overwrites into_index.
Inputs: a (required), b (required), doc_id (required), t, +2 more.
move_regionB
Move a region by (dx,dy), clearing its old location.
Inputs: dx (required), dy (required), doc_id (required), region (required), +5 more.
open_documentA
Import a PNG image as a new document from exactly one of png_base64 or path, quantizing to at most max_colors colors. Max 256x256.
Inputs: path, max_colors, png_base64.
outlineB
Draw a 1px border around all non-transparent pixels of the layer (or region). 'outside' draws into transparent pixels; 'inside' onto the shape's edge.
Inputs: mode (required), index (required), doc_id (required), clip, +5 more.
pack_sprite_sheetA
Pack document frames into a transparent PNG sprite sheet in row-major order and write it to disk. Sprites may have different sizes; cell dimensions default to the largest sprite. This is delivery output and returns no preview image.
Inputs: sprites (required), path, align, margin, +5 more.
paste_gridA
Bulk-write a region from grid text (one char per pixel, '.'=transparent, then 1-9, a-z, A-Z, '#', '@' for palette indices 1-63; rows top to bottom). The workhorse for blocking in shapes.
Inputs: x (required), y (required), doc_id (required), grid_text (required), +6 more.
paste_stampA
Paste a named stamp with optional flips and rotation. Transparent stamp pixels are skipped by default. Unlike other tools, paste_stamp ignores document symmetry by default: stamps are placed content, and mirroring would silently flip deliberate asymmetry.
Inputs: x (required), y (required), name (required), doc_id (required), +9 more.
preview_gifA
Render the animation as a GIF (on checkerboard background). Human-facing output only: most clients show a GIF as a single still in model context, so it verifies nothing — use view_frames to check motion.
Inputs: doc_id (required), loop, scale.
quantize_referenceA
Downsample a reference to the given size and map it to the document palette ('document') or to a fresh median-cut palette appended to the document ('auto'). Optionally write the result into a layer.
Inputs: width (required), doc_id (required), height (required), ref_id (required), +4 more.
rectC
Draw a rectangle (outline or filled).
Inputs: h (required), w (required), x (required), y (required), +8 more.
redoB
Redo previously undone edit(s).
Inputs: doc_id (required), steps, render.
remove_frameC
Delete a frame.
Inputs: frame (required), doc_id (required).
remove_layerB
Delete a layer and its pixels on every frame.
Inputs: layer (required), doc_id (required).
remove_palette_colorA
Delete a palette entry, remapping its pixels to remap_to. Higher indices shift down by one.
Inputs: index (required), doc_id (required), remap_to (required), render.
rename_layerC
Rename a layer.
Inputs: name (required), layer (required), doc_id (required).
reorder_frameC
Move a frame to a new position on the timeline.
Inputs: to (required), from (required), doc_id (required).
reorder_layerB
Move a layer to a new position in the stack (0 = bottom).
Inputs: to (required), layer (required), doc_id (required).
replace_colorA
Replace every pixel of one palette index with another on a layer (palette untouched). Optionally restricted to a region.
Inputs: doc_id (required), to_index (required), from_index (required), frame, +3 more.
resize_canvasA
Resize the canvas. Existing content is placed according to the anchor; new area is transparent. Clears saved selections.
Inputs: width (required), doc_id (required), height (required), anchor, +1 more.
save_selectionC
Store a named selection that region parameters can reference later.
Inputs: name (required), doc_id (required), region (required).
scatterA
Randomly scatter pixels from a set of palette indices across a region, at the given coverage density (0-1). For grain and noise: stone, dirt, wood texture. Deterministic for a given seed, so the same call reproduces the same grain. weights biases which indices are picked.
Inputs: doc_id (required), region (required), indices (required), clip, +7 more.
set_active_frameC
Set the default target frame for editing tools.
Inputs: frame (required), doc_id (required).
set_active_layerB
Set the default target layer for editing tools.
Inputs: layer (required), doc_id (required).
set_frame_durationC
Set how long a frame displays (ms).
Inputs: frame (required), doc_id (required), duration_ms (required).
set_layer_lockC
Lock or unlock a layer against edits.
Inputs: layer (required), doc_id (required), locked (required).
set_layer_visibilityC
Show or hide a layer.
Inputs: layer (required), doc_id (required), visible (required).
set_palette_colorB
Change the color (and optionally name) of a palette entry. All pixels using the index update visually.
Inputs: hex (required), index (required), doc_id (required), name, +1 more.
set_pixelsC
Set individual pixels to palette indices.
Inputs: doc_id (required), pixels (required), clip, frame, +3 more.
set_symmetryA
Set the document symmetry mode. While set, drawing and region tools mirror writes across the canvas center automatically.
Inputs: mode (required), doc_id (required).
set_tile_modeA
Set seamless-tiling mode. While set, drawing tools wrap across the tiling edges (a stroke off one side continues on the opposite side) and the tile_seams lint rule activates. 'horizontal' tiles left-right, 'vertical' top-bottom, 'both' for a full repeat. Use view_tiled to check seams.
Inputs: mode (required), doc_id (required).
shadeA
Darken or lighten existing pixels. A pixel whose color is part of a ramp steps along that ramp; otherwise it snaps to the nearest palette color in that direction. mode 'ramp' only moves ramp colors, 'nearest' ignores ramps, 'auto' (default) does ramp-then-nearest. Operates on the whole layer unless a region is given.
Inputs: doc_id (required), direction (required), mode, frame, +4 more.
shift_layerB
Translate the whole layer by (dx,dy), optionally wrapping around the edges.
Inputs: dx (required), dy (required), doc_id (required), wrap, +3 more.
split_sprite_sheetA
Split a regular PNG sprite sheet into separate pixel-mcp documents in row-major order. Provide exactly one of png_base64 or path. Supports sheet margins and spacing between cells; transparent cells are skipped by default.
Inputs: sprite_width (required), sprite_height (required), path, rows, +6 more.
swap_palette_indicesA
Swap two palette entries and all pixel references to them. The image looks unchanged.
Inputs: a (required), b (required), doc_id (required).
undoB
Undo the last edit(s).
Inputs: doc_id (required), steps, render.
viewA
Render the canvas (or a region) as a PNG. The primary perception tool: call it after every couple of edits.
Inputs: doc_id (required), frame, scale, coords, +3 more.
view_diffB
Compare the current state against a checkpoint: before, after, and changed-pixels panels.
Inputs: since (required), doc_id (required).
view_framesA
Render frames as a horizontal strip. With onion, each frame shows the previous frame at 40% opacity beneath it. This (plus per-frame view) is the perception tool for animation — use it to verify motion, not preview_gif.
Inputs: doc_id (required), onion, frames.
view_referenceB
Render a reference image, optionally beside the current canvas at matched display size.
Inputs: ref_id (required), frame, doc_id, beside_canvas.
view_textA
Read pixels as grid text (max 64x64; use view_window or a region for bigger canvases).
Inputs: doc_id (required), frame, layer, coords, +1 more.
view_tiledA
Render the canvas repeated reps x reps so tiling seams are visible, with the tile boundaries marked in faint red. The perception tool for seamless textures — a tile only reads as seamless when you see it tiled. Pairs with set_tile_mode and the tile_seams lint rule.
Inputs: doc_id (required), reps, frame, scale.
view_windowA
Zoomed view of a 16 or 32 px window plus a whole-canvas thumbnail with the window marked. The intended workflow for 64px+ canvases.
Inputs: cx (required), cy (required), size (required), doc_id (required), +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 70 tools
With 70 tools, some overlap is inevitable, but each tool has a detailed description that clarifies its distinct purpose. For example, the various view tools (view, view_text, view_window, etc.) serve specific viewing needs. A few tools like copy_region and cut_region could be confused, but overall disambiguation is strong.
All tools follow a consistent snake_case naming pattern, typically verb_noun (e.g., set_tile_mode, add_layer, reorder_frame). Even single-word verbs like view and undo fit the pattern. No mixing of conventions like camelCase or inconsistent verb forms.
70 tools is very high for an MCP server, far beyond the typical 3-15 range. While the domain of pixel art editing is complex, this count feels excessive and could overwhelm agents. Consolidating some tools (e.g., merging palette editing tools) would improve usability without losing functionality.
The tool surface covers the full workflow of pixel art creation: document management, layers, frames, palette editing, drawing primitives, region operations, stamps, selections, linting, and export. There are no obvious gaps for basic to intermediate pixel art tasks.
Maintenance
Related MCP Connectors
Generate, edit, style-match, turn, and animate game-ready pixel art at exact sizes.
Pixel art for your game, from your coding agent. Generate sprites, items and tiles in your game's style (64 to 1024 px), make variants, reskins and N/E/S/W directions, then edit everything in a full pixel art editor that syncs into your project. Works with Claude, Claude Code, Cursor and ChatGPT; sign in with OAuth, no API key.
Generate pixel art sprites, animations, 8-direction rotations and palettes for games.
Turn images into pixel art, extract and remap palettes, and upscale sprites without blur.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables AI assistants to create and manipulate pixel art by providing tools for drawing, layer management, and animation frame control. It supports exporting projects to PNG, GIF, and sprite sheet formats via the Model Context Protocol.2219Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to visually interact with LibreSprite for real-time pixel art creation and automated drawing with self-healing capabilities.MIT
- AlicenseAqualityBmaintenanceEnables AI to create pixel art in Aseprite through pixel-level drawing primitives, read canvas screenshots, and iterate until satisfied.317MIT
- AlicenseAqualityBmaintenanceEnables AI agents to create and edit pixel art in a live Aseprite window, with tools for drawing, selection, transformation, and animation.181,484 npm671MIT