aseprite-mcp
Drives Aseprite through its headless --script batch interface to create and edit real .aseprite files: creating canvases, managing layers, painting pixels and primitives, arranging frames and animation tags, managing palettes, and exporting PNG, GIF, sprite sheets with JSON metadata, and frame sequences.
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., "@aseprite-mcp create a 16x16 sprite named hero, draw a little potion, then export a gif"
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.
aseprite-mcp
An MCP server that lets an AI agent actually draw pixel art and frame-by-frame animation in Aseprite — not just export it. Create canvases, build up layers, paint pixel by pixel, arrange frames and animation tags, manage palettes, and export GIFs or sprite sheets.
It drives Aseprite through its official --script headless batch interface, and every operation lands on a real .aseprite file — so you can open the agent's work in Aseprite and keep editing by hand at any moment.
Zero dependencies: no npm install, just Node's standard library plus one Lua script.
Requirements
Aseprite 1.3+ (developed and fully verified against 1.3.18.3)
Node.js 18+
No npm packages
Related MCP server: aseprite-ai-artist
Install
Clone the repository and point your MCP client at src/server.js:
git clone https://github.com/baichuan4167-lang/aseprite-mcp.git
node aseprite-mcp/src/server.jsThe server looks for aseprite.exe in the usual install locations. If yours lives somewhere else, set ASEPRITE_PATH (see below).
Wiring it into an MCP client
The server speaks MCP over stdio. Add an entry to your client's MCP configuration:
{
"mcpServers": [
{
"name": "aseprite",
"command": "C:\\Program Files\\nodejs\\node.exe",
"args": ["<absolute path to aseprite-mcp>\\src\\server.js"],
"env": [
{ "name": "ASEPRITE_PATH", "value": "C:\\Program Files\\Aseprite\\aseprite.exe" },
{ "name": "ASEPRITE_MCP_WORKSPACE", "value": "<your project folder>" }
]
}
]
}
commandmust be an absolute path. On Windows, point it straight atnode.exeto avoidnpx/.cmdpath trouble. On macOS the executable isAseprite.app/Contents/MacOS/aseprite.
Environment variables
Variable | Default | Purpose |
| auto-detected | Full path to the Aseprite executable |
| session cwd | Workspace root |
|
| Where |
|
| Where exports land by default |
|
| Per-invocation timeout |
Quick start
1. aseprite_create_sprite document="hero" width=16 height=16
2. aseprite_pixels rows=["..kk..", ".kssk."] key={k:"#1a1c2c", s:"#ffcd75"} x=4 y=4
3. aseprite_view includeAscii=true <- actually LOOK before refining
4. aseprite_add_frames count=3 durationMs=100
5. aseprite_set_tag name="idle" from=0 to=3
6. aseprite_export_gif output="hero.gif" scale=6Coordinates are 0-based with the origin at the top-left; x grows right, y grows down.
How it works
agent ──MCP/stdio──▶ server.js ──writes a command JSON──▶ aseprite.exe -b --script aseprite_lib.lua
▲ │
└────────── reads a result JSON ◀──────────────┘Each tool call starts one headless Aseprite process, runs src/lua/aseprite_lib.lua, and reads the result back from a file.
The .aseprite file on disk is the single source of truth. That keeps calls independent and order-insensitive, and it means you can open the file in Aseprite and watch the agent work.
Concurrent calls that target the same document are serialised so they cannot clobber each other.
The 43 tools
Documents
Tool | Purpose |
| New canvas: size, colour mode (rgb / indexed / grayscale), initial frames, layer name, palette |
| Point the server at an existing |
| Canvas size, colour mode, layers, per-frame durations, animation tags |
| Aseprite location, workspace folders, documents already present |
Layers
Tool | Purpose |
| Add a layer, optionally placed with |
| Rename, opacity, visibility, blend mode |
| Delete a layer |
| Merge downward (composited by hand, no Aseprite command needed) |
Frames and animation
Tool | Purpose |
| Insert empty frames ( |
| Delete a frame |
| Copy a frame into the next slot — the usual starting point for animation |
| Reorder the timeline |
| Frame duration in ms, optionally for a range only |
| Copy one layer's artwork from one frame to another |
| Create/update an animation tag ( |
| Delete a tag |
| Set the loop range |
Drawing
Tool | Purpose |
| The workhorse. A rectangular block of pixels, given either as |
| Scattered individual pixels, each with its own colour |
| Draw a whole animation in one call: one character grid per frame |
| Primitives: lines, rectangles, ellipses, polygons, splines, flood fill, gradients, dithering, patterns, text |
| The same primitives on several frames, with a per-frame offset — the cheapest way to animate motion |
| Built-in 5×7 pixel font |
| Clear a rectangle to transparent |
aseprite_draw accepts these op values:
{op:"pixel", x, y, color}
{op:"line", x1, y1, x2, y2, color}
{op:"rect", x, y, w, h, color, filled}
{op:"ellipse", cx, cy, rx, ry, color, filled}
{op:"polygon", points:[[x,y],...], color, filled}
{op:"spline", points:[[x,y],...], color} smooth Catmull-Rom curve
{op:"fill", x, y, color, tolerance}
{op:"clear", x, y, w, h}
{op:"gradient",x, y, w, h, from, to, bands, direction}
{op:"dither", x, y, w, h, colors:[a,b], matrix:"bayer2|bayer4|bayer8", ratio}
{op:"pattern", x, y, w, h, tile:[[...],[...]]}
{op:"text", text, x, y, color, scale}Palette
Tool | Purpose |
| Set an explicit colour list |
| Built-in retro palettes — |
| Read the current palette |
| Reduce the artwork to N colours (exact, not approximated, when the piece already uses ≤ N) |
Several of these palettes are the work of other people and do not carry an explicit open-source licence. They are included because they are the standard palettes of the pixel-art community and ship with Aseprite itself, but if you plan to use them commercially, check the terms with the original author. See THIRD-PARTY-NOTICES.md for credits.
Canvas and transforms
Tool | Purpose |
| Resize the canvas without scaling artwork, with 9 anchor positions |
| Crop every frame |
| Integer upscale |
| Flip horizontally/vertically, rotate 90/180/270, add a 1 px outline |
| Replace one colour everywhere, with optional tolerance |
| Merge all visible layers |
Looking at the result
Tool | Purpose |
| Renders the frame to a PNG and returns it, optionally with an exact character pixel map |
| Previous frame ghosted red, next frame ghosted cyan — checks that motion reads |
| Read the final composited colour at one pixel |
| Dump a frame as a character grid plus palette legend (pure text, cheapest option) |
An agent cannot see the artwork unless you hand it over.
aseprite_viewandaseprite_onion_previeware its eyes — always look after drawing.
Import and export
Tool | Purpose |
| Single frame to PNG, optionally integer-scaled |
| Animated GIF, using each frame's own duration |
| One PNG per frame |
| Sprite sheet PNG plus JSON metadata (frame rects, durations, tags, layers) — game-engine ready |
| Bring in an external image: as a new document (tracing reference) or stamped onto a layer |
Suggested agent workflow
Pick a small canvas. 16–32 px for characters and items, 64–128 px for scenes. Pixel art reads best small.
Use layers. Outline, base colour, shading and highlights separately, so you can revise one without disturbing the others.
Use the compact formats.
aseprite_pixelswithrows+keycosts far fewer tokens thanpixels.Look. Call
aseprite_viewafter drawing instead of continuing on faith.Animate.
aseprite_duplicate_frame, then change only what moves withaseprite_draw_across_frames; verify withaseprite_onion_preview.Export. Sprite sheet + JSON for engines, GIF for previews.
Tests
No Aseprite required — CI runs these on every push:
node test/syntax-check.js # every JS file parses, no UTF-8 BOM anywhere, Lua compiles
node test/protocol.js # MCP handshake and the full tool catalog, without AsepriteRequires a real Aseprite installation:
node test/smoke.js # 85 end-to-end assertions across every tool
node test/slime.js # draws a green slime idle loop and exports it
node test/demo-art.js # draws a 4-frame walk cycle and exports itsmoke.js spawns the server over the real stdio protocol and checks each tool's
behaviour and its output files — including decoding the exported PNGs and
measuring the ink, because dimension-only checks once let a real scaling bug
through. The two demo scripts write art/*.aseprite and the GIF / sprite sheet /
frame sequence into out/.
Focused probes for the behaviours that caused the most trouble:
Script | Purpose |
| Decodes a sprite sheet and measures each frame's ink (catches scaling bugs) |
| Canvas resize and crop |
| Preview scaling |
| Character-grid multi-frame drawing |
| Palette quantization |
test/harness.js is a small MCP client over stdio, and test/png.js a
dependency-free PNG decoder, both used by the suites above.
Implementation notes
This Aseprite build's Lua API differs from the published documentation in several important ways. src/lua/aseprite_lib.lua works around them, and each workaround is documented at the call site. The highlights:
json.decodereturns userdata, not a table, sotype(x) == "table"fails → everything goes through anistable()predicate.Image:drawImage(src, Rectangle(...), x, y)clears the destination; only thedrawImage(src, x, y)form is reliable → all copies go throughblit().Sprite:newFrame(n)takes a duration in seconds, not an index, andreorderFrame/DuplicateFrame/Crop/MergeDowndo not exist → inserting, duplicating and reordering frames rebuilds the timeline in the required order.app.command.CanvasSizedoes not actually resize andSprite:resizescales but destroys cel contents → canvas operations rebuild the cels instead.Layer order comes from the writable
Layer.stackIndex(the value is the final position); there is noreorderLayer.In indexed mode
getPixelreturns a palette index, not an RGBA word → colour access goes through mode-aware helpers.Floats are fatal: JSON numbers decode as floats but Aseprite's Lua bindings demand integers, so every coordinate passes through
int().Aseprite's Lua has no bitwise operators, so base64 and bit twiddling are hand-written.
No UTF-8 BOM: a
.luafile with a BOM is an immediate syntax error.
Because DrawText is unavailable in batch mode, the server ships its own 5×7 bitmap font (the FONT table) that you can read and edit directly.
License
MIT
Available Tools
43 toolsaseprite_add_framesA
Insert empty frames. Frame indices are 0-based. Use mode to control where they land relative to an existing frame. Frames start empty, so draw into each one afterwards (aseprite_draw accepts a frame argument).
Inputs: mode, count, index, document, +1 more.
aseprite_add_layerB
Add a layer. Good practice for pixel art: separate layers for outline, base colour blocks, shading and highlights, so you can revise one without touching the others.
Inputs: name (required), above, below, opacity, +2 more.
aseprite_copy_celA
Copy one layer's artwork from one frame to another (a cel = one layer on one frame). Useful for holding poses, or stamping a base drawing across a new animation.
Inputs: toFrame (required), fromFrame (required), toLayer, document, +1 more.
aseprite_create_spriteA
Create a new pixel-art document and make it active. Every other tool works on this file. Coordinates are 0-based with the origin at the TOP-LEFT, x growing right and y growing down. Choose a small canvas: 16-32 px for characters and items, 64-128 px for scenes. Set frames up front when planning an animation.
Inputs: width, frames, height, palette, +6 more.
aseprite_cropB
Crop every frame to a rectangle. Use to trim dead space around the artwork.
Inputs: x (required), y (required), width (required), height (required), +1 more.
aseprite_drawA
Draw geometric primitives. Pass ops: an array of drawing operations applied in order, all in one call. Coordinates are 0-based from the top-left.
Operations: {op:"pixel", x, y, color} one pixel {op:"line", x1, y1, x2, y2, color} Bresenham line {op:"rect", x, y, w, h, color, filled:false} rectangle (w/h in pixels) {op:"ellipse", cx, cy, rx, ry, color, filled:false} ellipse; rx=ry for a circle {op:"polygon", points:[[x,y],...], color, filled:false} {op:"spline", points:[[x,y],...], color} smooth Catmull-Rom curve (good for tails, hair, limbs) {op:"fill", x, y, color, tolerance:0} flood fill from a seed pixel {op:"clear", x, y, w, h} make a region transparent {op:"gradient", x, y, w, h, from, to, bands:8, direction:"vertical"} banded ramp (classic pixel-art shading) {op:"dither", x, y, w, h, colors:[a,b], matrix:"bayer4", ratio:0.5} ordered dithering {op:"pattern", x, y, w, h, tile:[["#111","."],["#222","#333"]]} repeating 2-D tile {op:"text", text:"Hi", x, y, color, scale:1} built-in 5x7 pixel font
Each op may override layer and frame. Colors accept "#rrggbb", "#rrggbbaa" or a name.
Inputs: ops (required), frame, layer, document.
aseprite_draw_across_framesA
Apply the same set of drawing operations to several frames, optionally shifting them per frame. This is how you animate motion efficiently: describe the moving part once and give each frame an offset.
With no frames argument the ops are drawn on every frame. Pass frames as an array of indices, or of objects like {index:2, dx:3, dy:-1} to move the drawing by (dx, dy) on that frame.
Inputs: ops (required), layer, frames, document.
aseprite_duplicate_frameA
Copy a frame (all layers) to the next slot. The fastest way to build an animation: duplicate, then move or redraw only the parts that change.
Inputs: index (required), document, durationMs.
aseprite_eraseC
Erase a rectangular region (make it transparent) on a layer, across frames.
Inputs: x (required), y (required), width (required), height (required), +3 more.
aseprite_export_gifB
Export the animation as an animated GIF, using each frame's own duration. This is the quickest way to show the user a moving preview of what you animated.
Inputs: scale, output, document.
aseprite_export_pngA
Export the sprite as a flat PNG. By default every visible layer is composited and the first frame is written. Pass frame for a specific frame, or scale to upscale (integer scales keep pixels crisp).
Inputs: frame, scale, output, document.
aseprite_export_sequenceA
Export every frame as its own numbered PNG (hero_000.png, hero_001.png, ...). Use this when an engine or artist wants individual frames rather than a packed sheet.
Inputs: name, scale, format, document, +1 more.
aseprite_export_sprite_sheetA
Export a sprite sheet PNG plus optional JSON metadata - the standard way to hand pixel-art animations to a game engine. Use tag to export a single animation, sheetType to choose the layout, and padding options to avoid bleeding between tiles when the sheet is scaled.
Inputs: tag, rows, trim, scale, +14 more.
aseprite_fill_regionsA
Paint a set of individual pixels, each with its own colour, on one layer. Unlike aseprite_pixels this does not need a rectangular block, so it suits touching up scattered details, adding a highlight, or fixing stray pixels.
Inputs: cells (required), x, y, layer, +1 more.
aseprite_flattenA
Flatten all visible layers into one. Do this last, once the composition is settled.
Inputs: document.
aseprite_frames_from_gridsB
Draw a whole animation in one call: pass one character grid per frame and the tool writes each grid into its own frame. This is the fastest, most token-efficient way to produce a multi-frame animation. Grids are applied on a single layer; use x/y to offset them.
Inputs: grids (required), x, y, key, +2 more.
aseprite_get_paletteB
List the document's current palette as hex colours.
Inputs: document.
aseprite_import_imageA
Bring an external image into the pipeline. With asNewSprite it becomes a new document (useful for tracing over a reference, or for slicing an existing sprite sheet by hand). Otherwise it is drawn onto a layer of the active document at (x, y) - handy for stamping a palette reference, a logo, or a texture into a piece.
Inputs: source (required), x, y, frame, +3 more.
aseprite_infoA
Report a document's size, colour mode, layer list, frame list (with durations) and tags. Call this when you need to re-orient before editing, or to confirm a change landed.
Inputs: document.
aseprite_inspect_pixelsA
Dump a frame as text rows, one character per pixel, with a palette legend. Cheaper and more precise than an image when you only need to verify exact pixel positions or check a symmetric arrangement. "." is transparent.
Inputs: frame, document, maxPixels.
aseprite_load_paletteB
Apply a well-known retro palette by name, or load one from a .gpl/.ase file. Great starting point for a coherent piece. Presets: pico8, gameboy, nes, db16, db32, sweetie16, endesga32.
Inputs: file, preset, document.
aseprite_merge_layer_downA
Merge a layer into the one beneath it. Use at the end of a piece, or to flatten shading into a base layer.
Inputs: name, document.
aseprite_move_frameC
Reorder the timeline by moving one frame to another position.
Inputs: to (required), from (required), document.
aseprite_onion_previewA
Render a frame with the previous frame ghosted red and the next frame ghosted cyan. This is how animators check that motion reads correctly between frames - use it to verify the arc of a swing, a walk, or a bounce.
Inputs: frame (required), scale, document.
aseprite_openA
Point the server at an existing .aseprite file (absolute path, or a path inside the art folder) and make it active. Use this to continue work on a file, or to work on art the user already has.
Inputs: document (required).
aseprite_pick_colorB
Read the final composited colour at a single pixel. Returns hex plus RGBA.
Inputs: x (required), y (required), frame, document.
aseprite_pixelsA
Draw a rectangular block of pixels - the main tool for putting actual artwork into a sprite. The origin is the top-left of the canvas; x grows right, y grows down.
Two ways to pass data:
pixels: array of rows, each row an array of hex colours. null or "." leaves the pixel transparent.rows+key: rows given as compact strings where each character is looked up inkey. Much shorter for larger art - for example rows: ["kksskk","kssssk"] with key: {"k":"#1a1c2c","s":"#ffcd75"}.
All rows must be the same length. Drawing only touches the pixels you supply; everything else on the canvas is left alone. To keep revisions cheap, send one small block per part (outline, fill, shading) rather than repainting the whole sprite.
Inputs: x, y, key, rows, +5 more.
aseprite_quantize_paletteA
Reduce the artwork to N colours (k-means) and snap every pixel to the result. Use this to tame a messy palette or to hit a retro constraint like 16 colours.
Inputs: count (required), remap, document.
aseprite_remove_frameB
Delete a frame. Refuses to remove the last remaining frame.
Inputs: index (required), document.
aseprite_remove_layerB
Delete a layer and everything drawn on it.
Inputs: name (required), document.
aseprite_remove_tagC
Delete an animation tag by name.
Inputs: name (required), document.
aseprite_replace_colorA
Replace every pixel of one colour with another across the whole document. Handy for recolouring a character, or swapping a shade across all frames at once.
Inputs: to (required), from (required), document, tolerance.
aseprite_resize_canvasA
Change the canvas size without scaling the artwork. Use it to add room for an attack animation or breathing space around a character.
Inputs: width (required), height (required), anchor, document.
aseprite_scale_spriteB
Scale the whole sprite (all frames and layers). Use integer factors like 2 or 4 for pixel-art upscaling; non-integer scaling blurs pixels unless Aseprite is set to nearest neighbour.
Inputs: width (required), height (required), document.
aseprite_set_frame_durationA
Set frame durations in milliseconds. Applies to a range when from/to are given, otherwise to every frame. 100 ms = 10 fps is a good default; 40-80 ms suits fast actions like a run cycle.
Inputs: durationMs (required), to, from, document.
aseprite_set_layerB
Change a layer's opacity, visibility, blend mode, or rename it.
Inputs: name (required), newName, opacity, visible, +2 more.
aseprite_set_loopC
Restrict playback/export looping to a frame range of the whole sprite.
Inputs: to, from, document.
aseprite_set_paletteB
Replace the document palette with an explicit colour list. In indexed mode this is the actual set of colours the artwork can use - which is how you keep a piece to a tight, coherent palette.
Inputs: colors (required), size, remap, document.
aseprite_set_tagA
Create or update a named animation tag spanning a frame range (e.g. "walk" frames 0-5). Tags are what game engines export as separate animations, and users can play them in the Aseprite timeline.
Inputs: to (required), from (required), name (required), color, +3 more.
aseprite_statusA
Report the Aseprite installation the server found, the workspace folders it uses, and the active document. Call this first if a tool fails, or to learn where files are being written.
Inputs: no named parameters.
aseprite_textA
Draw text with the built-in 5x7 pixel font. Suitable for labels, UI mockups and small titles. Non-ASCII characters render as "?". Use scale 2 or 3 for larger text that stays crisp.
Inputs: x (required), y (required), text (required), color, +4 more.
aseprite_transformB
Flip, rotate by 90/180/270, or add a 1 px outline to the artwork. Outlines are the classic way to make sprite art read clearly against any background.
Inputs: action (required), axis, color, layer, +5 more.
aseprite_viewA
Render a frame to a PNG image so you can actually LOOK at the artwork and judge it. Use this after drawing, and before telling the user the piece is finished. Set includeAscii: true to also get an exact character map of the pixels, which is the most precise way to verify positions and colours.
Inputs: frame, scale, layers, document, +1 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 43 tools
Most tools have clearly distinct domains, especially frame, layer, palette, and export operations. The main ambiguity is in the drawing surface: aseprite_pixels, aseprite_fill_regions, aseprite_draw, aseprite_draw_across_frames, aseprite_frames_from_grids, and aseprite_text all write pixels, though their descriptions explain different use cases.
Every tool uses the same aseprite_ prefix followed by a snake_case verb or verb-object name. The naming is predictable and readable throughout the set.
43 tools is well above the practical range for a single MCP server and will make tool selection harder for agents. Although Aseprite is a broad domain, the set includes many specialized operations that could be consolidated or grouped.
The surface covers document lifecycle, layers, frames, tags, drawing, palettes, transforms, previews, imports, and multiple export formats. Minor gaps remain, such as explicit undo/redo or layer reordering, but core pixel-art and animation workflows are well supported.
Related MCP Connectors
Generate, edit, style-match, turn, and animate game-ready pixel art at exact sizes.
Turn images into pixel art, extract and remap palettes, and upscale sprites without blur.
Generate pixel art sprites, animations, 8-direction rotations and palettes for games.
AI game assets for agents: consistent sprites, 2D animations, tiles, maps, music and engine exports.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to drive Aseprite for pixel-art creation, including sprite setup, grid-based drawing, layer/frame/tag management, reference image import, and export, with rendered previews after every mutation.MIT
- AlicenseAqualityBmaintenanceEnables AI agents to create and edit pixel art in a live Aseprite window, with tools for drawing, selection, transformation, and animation.18819 npm571MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to create, inspect, edit, and export pixel art, sprites, animations, and spritesheets using headless Aseprite, and to convert arbitrary images into indexed pixel art.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI coding agents to create, draw, and refine Aseprite documents through deterministic canvas, layer, frame, tag, and palette tools, with pixel analyzers, style validation, and preview/diff visual feedback. It also supports spritesheet and animated GIF export, producing multi-layer game-ready assets.1MIT