Aseprite MCP
MCP server for interacting with the Aseprite API
Documentation
Aseprite MCP Tools
A Python MCP server that gives AI assistants full control over Aseprite for creating pixel art and animated sprites.
123 tools across 18 categories — canvas, drawing, layers, animation, palettes, effects, slices, tilemaps, exports, visual-feedback/analysis tools, and a raw Lua escape hatch. The tool set is designed so an LLM has everything it needs to produce good pixel art, not just primitives: shading ramps with hue shifting, ordered dithering, outlines, retro palette presets with quantization, onion-skin renders, and frame diffing, bounds and lint checks for animation work.
Examples
Cosy room (Opus 5.5)
![]() |
| An isometric bedroom at dusk with a girl doing her homework, animated as a lofi-style loop. 128×128, 16-frame loop, exported at 4×. |
Swordsman: Fable 5 vs Opus 5.5
| Fable 5 | Opus 5.5 |
|---|---|
![]() | ![]() |
![]() | ![]() |
| The benchmark swordsman, then a slash from windup to follow-through. 48×48, 4-frame slash. | Opus 5.5 critiqued the Fable 5 sprite and redrew it at the same size. 48×48, 8-frame slash. |
The Fable 5 tasks recreate the benchmark from Draw Me a Swordsman by Lj Miranda, whose findings inspired this server's expanded toolset.
Slime (Opus 5.5)
![]() |
| A green slime. 32×32 still, exported at 8×. |
![]() |
| The slime bouncing to the right with squash and stretch. 43-frame animation, exported at 4×. |
Swordsman vs. slime (Opus 5.5)
![]() |
| The swordsman slashes the slime, which flashes red, loses health and pops a damage number. 80×58, 15 frames, exported at 5×. |
Related Projects
Aseprite Live – Watch an AI agent draw pixel art in Aseprite, live, in your browser
Tool Categories
| Category | Tools | Description |
|---|---|---|
| Canvas | 7 | Create sprites, manage layers/frames, set the active state |
| Drawing | 16 | Pixels, text rows, lines, rectangles, circles, ellipses, polygons, paths, fills, gradients, isometric boxes |
| Text | 3 | Draw and measure text with bitmap sprite-sheet or TrueType fonts |
| Layers | 7 | Delete, rename, duplicate, reorder, blend modes, merge, flatten |
| Selection & Regions | 4 | Move, copy, and erase rectangular regions or colors |
| Effects | 12 | Outlines, color replacement, HSL adjustment, ordered dithering, native Aseprite filters |
| Animation | 24 | Frames, cels, tags, tweening with easing, propagation |
| Palette | 8 | Get/set palettes, retro presets, color ramps, quantization, color modes |
| Transform | 4 | Flip, rotate, resize, crop |
| Slices | 5 | Named regions, 9-patch centers, pivot points |
| Tilemap | 5 | Tilemap layers, tileset editing, tile placement |
| Export & Import | 8 | PNG, GIF, sprite sheets, per-layer/per-tag export, previews on a background, image import |
| Inspection | 6 | Read pixels (one layer, the visible composite, or as text rows) and sprite metadata |
| Analysis & Visual Feedback | 6 | Sprite previews the model can see, onion-skin renders, frame diffing, bounds, lint, color statistics |
| Quality | 4 | Validate and sanitize animation consistency |
| Scene | 1 | Copy layers between sprite files |
| Preview & Guide | 3 | Local HTTP preview server, workflow guide |
| Scripting | 1 | Raw Lua escape hatch for anything not covered above |
Canvas
| Tool | Description |
|---|---|
create_canvas | Create a new sprite with the given dimensions |
add_layer | Add a new layer, optionally inside a named group |
add_group | Add a new (optionally nested) group layer |
add_frame | Append a new frame |
set_frame | Set the active frame |
set_frame_duration | Set one frame's duration in ms |
set_layer | Set the active layer (optionally creating it) |
Drawing
All _at variants target a specific layer/frame and can create the cel on demand. Coordinates are sprite-global.
draw_rows(filename="mug.aseprite", layer_name="mug", frame_index=1, x=4, y=2,
rows=[".WWWW..",
".WWWWKK",
".WWWW.K",
"..WW.K."],
legend={"W": "#F4F4F4", "K": "#8B5A2B"}, outline="#1A1C2C")
. (or any character missing from the legend) leaves a pixel untouched; read_rows returns pixels in the same format, so an edit is read, change a few characters, draw again.
| Tool | Description |
|---|---|
draw_pixels | Plot individual pixels with per-pixel colors |
draw_pixels_at | Plot pixels on a specific layer/frame |
draw_line / draw_line_at | Lines with thickness |
draw_rectangle / draw_rectangle_at | Outlined or filled rectangles |
draw_circle / draw_circle_at | Outlined or filled circles |
draw_ellipse_at | Outlined or filled ellipses with separate x/y radii |
draw_polygon | Outlined or filled polygons from a point list |
draw_path | Polyline through a point list with thickness |
fill_area / fill_area_at | Paint-bucket flood fill |
apply_gradient_rect | Smooth linear gradient fill in a rectangle |
draw_rows | Pixels as rows of characters plus a colour legend, like ASCII art, with an optional 1px outline — the compact way to place hand-chosen detail (faces, hands, animals, handles) |
draw_iso_box | Solid 2:1 isometric box with clean 2-1 edges and a colour per face, for furniture and props |
Text
Aseprite's Lua API has no text drawing, so glyphs are rasterised on the server and composited into the sprite. Two font backends are supported: bitmap sprite-sheet fonts (the right choice for pixel art — glyphs are already pixels and size scales them by whole numbers) and TrueType .ttf/.otf files, rendered without antialiasing unless you ask for it.
Fonts are discovered in ~/.aseprite-mcp/fonts: a bitmap font is a directory containing font.json plus its sheet PNGs; a TrueType font is just the font file. Installed system fonts are listed too. font also accepts a direct path.
| Tool | Description |
|---|---|
list_text_fonts | List the bitmap and TrueType fonts available to draw_text |
measure_text | Width, height, advance and baseline extents — size a panel or centre a label without redrawing to find out |
draw_text | Draw text with anchors, faux bold, outline, drop shadow and letter spacing |
Call measure_text first when the layout depends on the text: it returns the same metrics draw_text will use, so a label can be centred or a plate sized in one pass.
measure_text(text="MODERATOR", font="minecraft", size=2, bold=1)
-> width=115 height=15 advance_width=117 above_baseline=14 below_baseline=1 left_bearing=0
draw_text(filename="badge.aseprite", text="MODERATOR", x=64, y=17,
font="minecraft", size=2, anchor="baseline", bold=1,
color="#FFFFFF", shadow_color="#14237A")
Anchors are topleft/top/topright, left/center/right, bottomleft/bottom/bottomright, plus baselineleft/baseline/baselineright for pinning several labels to one baseline. Outline and shadow grow the stamp but never move the glyphs.
font.json
{
"name": "my-font",
"letter_gap": 1,
"space_width": 3,
"sheets": [{
"file": "sheet.png",
"cell_w": 8, "cell_h": 8,
"ascent": 7,
"chars": [" !\"#$%&'()*+,-./", "0123456789:;<=>?"]
}],
"overrides": {
"86": {"ascent": 7, "rows": ["#...#", "#...#", ".#.#.", "..#.."]}
}
}
Each sheet carries its own ascent, so sheets with different cell sizes still share a baseline — that is how a compact ASCII sheet and a taller accented sheet combine into one run. origin shifts the cell grid when the sheet has a margin, ink_rule: "dark" reads sheets that use an opaque white background to delimit variable-width glyph boxes, and advance: "box" takes the advance from the box rather than the ink. overrides replaces individual glyphs with hand-drawn rows, which is useful when a sheet ships a glyph that is off-centre or too tall to sit on the line.
Layers
| Tool | Description |
|---|---|
delete_layer | Delete a layer by name |
rename_layer | Rename a layer |
duplicate_layer | Duplicate a layer with all cels, opacity, and blend mode, optionally into a group |
reorder_layer | Move a layer to a position in the stack |
set_layer_blend_mode | Set blend mode (multiply, screen, overlay, ... 19 modes) |
merge_layer_down | Merge a layer into the one below it |
flatten_sprite | Flatten all layers into one |
Selection & Regions
| Tool | Description |
|---|---|
move_region | Cut a rectangle of pixels and paste it elsewhere |
copy_region | Copy a rectangle to another position, layer, or frame |
erase_region | Make a rectangle transparent |
erase_color | Magic-eraser: make all pixels of a color transparent (with tolerance) |
Effects
The pixel-art toolbox: clean outlines, palette-respecting blends, and shading variants.
| Tool | Description |
|---|---|
outline_cel | Add a 1px outline around all opaque pixels, or only around the shapes in a region |
replace_color | Replace one color with another (with tolerance), preserving alpha |
adjust_hsl | Shift hue/saturation/lightness of a cel — palette swaps, night scenes, shadows |
apply_dither_gradient | Two-color gradient using Bayer 4×4 ordered dithering |
apply_dither_pattern | Uniform dithered mix of two colors at a given density |
Native filters run Aseprite's own engine commands on a layer and frame:
| Tool | Description |
|---|---|
outline_native | Aseprite's Outline command around opaque pixels |
adjust_hsl_native | Aseprite's Hue/Saturation/Lightness filter |
adjust_brightness_contrast | Brightness/Contrast filter |
invert_colors | Colour inversion |
apply_convolution | Convolution filter: blur, sharpen, edge, emboss … |
list_convolution_matrices | List the built-in matrices for apply_convolution |
extract_palette | Build an optimal palette from the sprite (native colour quantization) |
Animation
| Tool | Description |
|---|---|
add_frames | Append N frames with optional duration |
delete_frame | Delete a frame |
set_frame_duration_all | Set every frame's duration |
duplicate_frame_range | Duplicate a frame range N times |
copy_frame / propagate_frame_to_range | Copy all cels of a frame to other frames |
create_cel / clear_cel / copy_cel | Cel lifecycle on a layer/frame |
propagate_cels | Copy selected layers' cels across a frame range |
set_cel_position | Place a cel at x,y |
set_cel_opacity | Set a single cel's opacity |
offset_cel_positions | Shift cels by a delta across frames |
tween_cel_positions | Linear position tween across frames |
tween_cel_positions_eased | Position tween with easing (ease_in/out, smoothstep) |
tween_cel_opacity_eased | Opacity tween with easing |
tween_cel_scale_eased | Scale tween with easing and anchor |
oscillate_cel_positions | Sine-wave motion (bobbing, breathing, hovering) |
set_tag / delete_tag | Animation tags with direction (forward/reverse/pingpong) |
set_layer_visibility / set_layer_opacity | Layer-level visibility and opacity |
set_onion_skin | Configure onion-skin UI prefs (see render_onion_skin for batch use) |
get_sprite_info | Sprite metadata: size, layers, frames, durations, tags |
Palette
| Tool | Description |
|---|---|
get_palette | Read the palette as hex colors |
set_palette | Set the palette from a list of hex colors |
list_palette_presets | List built-in retro palettes |
apply_palette_preset | Apply a preset: gameboy, pico8, c64, cga, dawnbringer16, dawnbringer32, grayscale_4, monochrome |
generate_color_ramp | Build a dark→light shading ramp with hue shifting from a base color |
quantize_to_palette | Snap every pixel to the nearest palette color |
remap_colors_in_cel_range | Remap specific colors across a frame range |
set_color_mode | Convert between RGB, grayscale, and indexed |
Transform
| Tool | Description |
|---|---|
flip_layer | Flip a cel horizontally or vertically |
rotate_layer | Rotate a cel 90/180/270° |
resize_canvas | Scale the sprite to new dimensions |
crop_canvas | Crop to a rectangle |
Slices
| Tool | Description |
|---|---|
create_slice | Create a named rectangular region |
set_slice_center | Set the 9-patch stretchable center |
set_slice_pivot | Set the pivot point |
list_slices | List all slices with bounds, centers, pivots as JSON |
delete_slice | Delete a slice |
Tilemap
| Tool | Description |
|---|---|
create_tilemap_layer | Add a tilemap layer with its own tileset and tile grid |
draw_on_tile | Paint pixels into a tileset tile (auto-appends new tiles) |
set_tiles | Place tiles on the map by grid position |
get_tile_at | Read which tile occupies a grid cell |
get_tilemap_info | Tile size, tile count, and map dimensions as JSON |
Export & Import
| Tool | Description |
|---|---|
export_sprite | Export to PNG, GIF, JPG, ... |
export_frame | Export one frame as PNG with integer upscaling (to look at your work, use view_sprite) |
export_spritesheet | Sprite sheet (horizontal/vertical/rows/columns/packed) with optional JSON metadata and per-tag filtering |
export_layers | One PNG per layer |
export_tag | Export an animation tag as GIF or PNG sequence |
export_animation | GIF or PNG sequence of every frame or one tag, scaled, optionally on a solid background — the sprite itself is never changed |
import_image_as_layer | Import a PNG into a layer (references, premade parts) |
copy_sprite | Duplicate the .aseprite file |
Inspection
| Tool | Description |
|---|---|
get_pixel_color | Read one pixel's RGBA |
get_pixels_rect | Read a rectangle of pixels as JSON |
get_composite_pixel / get_composite_rect | Read what is visible (all layers composited) at a pixel or over a rectangle |
read_rows | Read a region as rows of characters plus a legend — the compact format draw_rows takes back |
get_sprite_info | Sprite metadata (also listed under Animation) |
Analysis & Visual Feedback
Batch-mode equivalents of what a human artist gets from the Aseprite UI.
| Tool | Description |
|---|---|
view_sprite | Return an image of the frames straight to the model — zoomed, with pixel grid and coordinate rulers so it can fix exact pixels |
render_onion_skin | Render a frame over translucent ghosts of neighboring frames — check motion continuity without opening Aseprite |
compare_frames | Diff two frames, optionally one layer or region (did the feet move?): changed pixel count, percentage, bounding box |
get_bounds | Bounding box of the drawn pixels per frame, optionally one layer — measure rise and fall, ground line, canvas edges |
lint_sprite | Detached pieces, lone pixels and orphan pixels, with coordinates |
get_color_stats | Color histogram of a frame — catches palette drift and near-duplicate colors |
view_sprite is the drawing loop's eyes: it returns the picture as MCP image content, so the model sees its work without exporting a file and opening it, and works in clients that cannot read local files. Pick frames with "3", "1-4" or "1,3,6-8", layer_name to isolate one layer, and region="x,y,w,h" to zoom into a detail with rulers still in sprite coordinates.
Quality
| Tool | Description |
|---|---|
ensure_layers_present | Create missing cels for layers across a frame range |
validate_scene | Report missing layers/cels as JSON |
audit_animation | Audit frames for overlaps and out-of-range layer activity |
animation_sanitize | Normalize layer order, coverage, and overlaps |
Scene
| Tool | Description |
|---|---|
copy_layers_between_sprites | Copy layers by name from one .aseprite file to another |
Preview & Guide
| Tool | Description |
|---|---|
start_preview_server / stop_preview_server | Serve exported files over local HTTP |
animation_workflow_guide | How to animate well: key poses, anticipation, squash and stretch, timing (character, idle, environment) |
The server also sends short pixel-art rules as MCP instructions when a client connects (silhouette first, one light direction, clean lines, check each pass), so the model gets them without calling anything.
Scripting
| Tool | Description |
|---|---|
run_lua_script | Execute arbitrary Aseprite Lua (API docs) in batch mode. The last resort, for what no dedicated tool covers: one script can batch many operations into a single Aseprite launch. Remember to spr:saveAs(spr.filename) and print() your results. ⚠️ Runs unrestricted code on the host — only pass scripts you trust. |
Recommended Workflow for LLMs
- Plan the palette first:
generate_color_rampfor each material (skin, armor, blade), orapply_palette_presetfor a retro look. - Build in layers: background / body / equipment / effects, so parts can be animated and edited independently.
- Draw coarse to fine: silhouette with
draw_rectangle_at/draw_ellipse_at/fill_area_at, then refine withdraw_rows. - Look at your work:
view_spriteafter every pass (regionto zoom in), fix, repeat. Useget_color_statsto keep the palette tight andlint_spriteto catch stray and detached pixels. - Shade with intent:
adjust_hslfor shadow layers,apply_dither_gradientfor blends,outline_celfor readability. - Animate with the cel tools:
propagate_cels, thentween_cel_positions_eased/oscillate_cel_positions; verify withview_sprite,compare_framesandget_bounds; export withexport_animationorexport_tag.
Docker Usage
Quick Start
Build and run the Docker image:
docker build -t aseprite-mcp:latest .
docker run -it --rm aseprite-mcp:latest
Or use the provided build scripts:
- Linux/macOS:
chmod +x build-docker.sh && ./build-docker.sh - Windows:
.\build-docker.ps1
Using Docker Compose
# Production
docker-compose up aseprite-mcp
# Development mode
docker-compose --profile dev up aseprite-mcp-dev
See DOCKER.md for detailed Docker setup instructions.
Optional: Install Aseprite via Steam
To have the container install Aseprite via SteamCMD at startup, provide Steam credentials:
# Create a .env with STEAM_USERNAME/STEAM_PASSWORD (and optional STEAM_GUARD_CODE)
# Then
docker run --rm -i --env-file .env aseprite-mcp:latest
If installed, the binary will be at /opt/steamapps/common/Aseprite/aseprite and ASEPRITE_PATH will be picked up automatically.
Local Installation
Prerequisites
- Python 3.13+
uvpackage manager- Aseprite (set
ASEPRITE_PATHin.envif it is not on your PATH)
Installation:
{
"mcpServers": {
"aseprite": {
"command": "/opt/homebrew/bin/uv",
"args": [
"--directory",
"/path/to/repo",
"run",
"-m",
"aseprite_mcp"
]
}
}
}







