Skip to main content
Glama

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.js

The 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>" }
      ]
    }
  ]
}

command must be an absolute path. On Windows, point it straight at node.exe to avoid npx / .cmd path trouble. On macOS the executable is Aseprite.app/Contents/MacOS/aseprite.

Environment variables

Variable

Default

Purpose

ASEPRITE_PATH

auto-detected

Full path to the Aseprite executable

ASEPRITE_MCP_WORKSPACE

session cwd

Workspace root

ASEPRITE_MCP_ART_ROOT

<workspace>/art

Where .aseprite documents live

ASEPRITE_MCP_EXPORT_ROOT

<workspace>/out

Where exports land by default

ASEPRITE_MCP_TIMEOUT_MS

120000

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=6

Coordinates 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

aseprite_create_sprite

New canvas: size, colour mode (rgb / indexed / grayscale), initial frames, layer name, palette

aseprite_open

Point the server at an existing .aseprite file and make it active

aseprite_info

Canvas size, colour mode, layers, per-frame durations, animation tags

aseprite_status

Aseprite location, workspace folders, documents already present

Layers

Tool

Purpose

aseprite_add_layer

Add a layer, optionally placed with below / above

aseprite_set_layer

Rename, opacity, visibility, blend mode

aseprite_remove_layer

Delete a layer

aseprite_merge_layer_down

Merge downward (composited by hand, no Aseprite command needed)

Frames and animation

Tool

Purpose

aseprite_add_frames

Insert empty frames (before / after / end)

aseprite_remove_frame

Delete a frame

aseprite_duplicate_frame

Copy a frame into the next slot — the usual starting point for animation

aseprite_move_frame

Reorder the timeline

aseprite_set_frame_duration

Frame duration in ms, optionally for a range only

aseprite_copy_cel

Copy one layer's artwork from one frame to another

aseprite_set_tag

Create/update an animation tag (walk), with direction and looping

aseprite_remove_tag

Delete a tag

aseprite_set_loop

Set the loop range

Drawing

Tool

Purpose

aseprite_pixels

The workhorse. A rectangular block of pixels, given either as pixels (2-D array of hex colours) or as rows + key (compact character grid)

aseprite_fill_regions

Scattered individual pixels, each with its own colour

aseprite_frames_from_grids

Draw a whole animation in one call: one character grid per frame

aseprite_draw

Primitives: lines, rectangles, ellipses, polygons, splines, flood fill, gradients, dithering, patterns, text

aseprite_draw_across_frames

The same primitives on several frames, with a per-frame offset — the cheapest way to animate motion

aseprite_text

Built-in 5×7 pixel font

aseprite_erase

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

aseprite_set_palette

Set an explicit colour list

aseprite_load_palette

Built-in retro palettes — pico8 gameboy nes db16 db32 sweetie16 endesga32 — or a .gpl / .ase file

aseprite_get_palette

Read the current palette

aseprite_quantize_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

aseprite_resize_canvas

Resize the canvas without scaling artwork, with 9 anchor positions

aseprite_crop

Crop every frame

aseprite_scale_sprite

Integer upscale

aseprite_transform

Flip horizontally/vertically, rotate 90/180/270, add a 1 px outline

aseprite_replace_color

Replace one colour everywhere, with optional tolerance

aseprite_flatten

Merge all visible layers

Looking at the result

Tool

Purpose

aseprite_view

Renders the frame to a PNG and returns it, optionally with an exact character pixel map

aseprite_onion_preview

Previous frame ghosted red, next frame ghosted cyan — checks that motion reads

aseprite_pick_color

Read the final composited colour at one pixel

aseprite_inspect_pixels

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_view and aseprite_onion_preview are its eyes — always look after drawing.

Import and export

Tool

Purpose

aseprite_export_png

Single frame to PNG, optionally integer-scaled

aseprite_export_gif

Animated GIF, using each frame's own duration

aseprite_export_sequence

One PNG per frame

aseprite_export_sprite_sheet

Sprite sheet PNG plus JSON metadata (frame rects, durations, tags, layers) — game-engine ready

aseprite_import_image

Bring in an external image: as a new document (tracing reference) or stamped onto a layer


Suggested agent workflow

  1. Pick a small canvas. 16–32 px for characters and items, 64–128 px for scenes. Pixel art reads best small.

  2. Use layers. Outline, base colour, shading and highlights separately, so you can revise one without disturbing the others.

  3. Use the compact formats. aseprite_pixels with rows + key costs far fewer tokens than pixels.

  4. Look. Call aseprite_view after drawing instead of continuing on faith.

  5. Animate. aseprite_duplicate_frame, then change only what moves with aseprite_draw_across_frames; verify with aseprite_onion_preview.

  6. 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 Aseprite

Requires 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 it

smoke.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

test/probe-sheet-verify.js

Decodes a sprite sheet and measures each frame's ink (catches scaling bugs)

test/probe-canvas.js

Canvas resize and crop

test/probe-view.js

Preview scaling

test/probe-grids.js

Character-grid multi-frame drawing

test/probe-quantize.js

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.decode returns userdata, not a table, so type(x) == "table" fails → everything goes through an istable() predicate.

  • Image:drawImage(src, Rectangle(...), x, y) clears the destination; only the drawImage(src, x, y) form is reliable → all copies go through blit().

  • Sprite:newFrame(n) takes a duration in seconds, not an index, and reorderFrame / DuplicateFrame / Crop / MergeDown do not exist → inserting, duplicating and reordering frames rebuilds the timeline in the required order.

  • app.command.CanvasSize does not actually resize and Sprite:resize scales 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 no reorderLayer.

  • In indexed mode getPixel returns 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 .lua file 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 tools
aseprite_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:

  1. pixels: array of rows, each row an array of hex colours. null or "." leaves the pixel transparent.

  2. rows + key: rows given as compact strings where each character is looked up in key. 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

    B3.4/5.0

    Scored across 43 tools

    Disambiguation4/5

    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.

    Naming Consistency5/5

    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.

    Tool Count2/5

    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.

    Completeness4/5

    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

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables 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
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI agents to create and edit pixel art in a live Aseprite window, with tools for drawing, selection, transformation, and animation.
      18
      819 npm
      571
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables 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
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables 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.
      1
      MIT