MCP server exposing headless Aseprite as five consolidated tools, so AI agents can create, inspect, edit and export pixel art — sprites, animations, spritesheets — and convert arbitrary images (e.g. ComfyUI renders) into indexed pixel art.
Aseprite runs fully batch (aseprite -b --script <lua>); no GUI ever opens.
State lives in .aseprite project files on disk plus a SQLite audit log —
every tool call is one validated headless run that reports the files actually
written.
| Tool | Actions | Purpose |
|---|---|---|
aseprite_sprite |
new, list, info, palette, frames, layers | Project lifecycle + inspection (incl. flattened pixel grids, capped at 128x128) |
aseprite_draw |
map, pixels, shape, clear | Drawing — map is the ASCII pixel-map authoring path |
aseprite_export |
png, gif, sheet | Flattened per-frame PNGs, animated GIF, spritesheets (integer upscaling) |
aseprite_pixelate |
convert | Image → indexed pixel art (downscale + color quantization) |
aseprite_script |
run | Raw Lua escape hatch (full Aseprite scripting API) |
- Serialized projects: calls that touch the same
.asepriterun one at a time (per-project mutex) — parallel tool calls can't interleave read-modify- -write cycles and lose pixels. - Backups: every mutating action snapshots the project to
<file>.aseprite.bakfirst; the response reports thebackuppath. One slot, newest-wins — recovery from a bad draw is always one rename away. - Fresh provenance: exports stat the output directory and only report files written by that run.
aseprite_scriptis the trust boundary: it runs arbitrary Lua with the full scripting API and no path sandboxing — that's what makes it the escape hatch. Run this server only where its Lua privileges are acceptable.
Language models draw best when they can read the art. A map is a palette header plus one character per pixel:
PAL:
K #1a1c2c
Y #ffcd75
MAP:
..KKKK..
.KYYYYK.
.KYYYYK.
..KKKK..
. is always transparent; multiple MAP: sections become successive
animation frames (draw only what changes — new frames copy the previous
cel). The server validates the map (row widths, declared characters, hex
format) before Aseprite ever runs.
- Binary:
F:/ComfyUI/tools/aseprite/aseprite.exe(v1.3.18.3, self-compiled) - Projects:
F:/ComfyUI/aseprite_output/projects/*.aseprite - Exports:
F:/ComfyUI/aseprite_output/(PNGs here load straight into ComfyUILoadImagenodes) - Generated scripts:
F:/ComfyUI/aseprite_output/mcp-scripts/(kept for debugging) - Audit/provenance DB:
data/aseprite-mcp.db
npm install
npm run build
npm test # no live Aseprite needed (stubbed runner)
npm run smoke # end-to-end against the real binary
Register (ZCode cli/config.json → mcp.servers):
"aseprite": {
"command": "<node>",
"args": ["F:\\Github\\mnehmos.aseprite.mcp\\dist\\index.js"],
"env": { "ASEPRITE_BIN": "F:\\ComfyUI\\tools\\aseprite\\aseprite.exe" },
"enabled": true
}| Var | Default |
|---|---|
ASEPRITE_BIN |
F:/ComfyUI/tools/aseprite/aseprite.exe |
ASEPRITE_PROJECTS_DIR |
F:/ComfyUI/aseprite_output/projects |
ASEPRITE_EXPORT_DIR |
F:/ComfyUI/aseprite_output |
ASEPRITE_SCRIPTS_DIR |
F:/ComfyUI/aseprite_output/mcp-scripts |
ASEPRITE_TIMEOUT_MS |
60000 |
ASEPRITE_MCP_DB |
<repo>/data/aseprite-mcp.db |
- Stateless runs, durable state. Each call loads the
.asepritefresh, mutates in memory, saves explicitly. Destructive Aseprite commands are safe on the in-memory copy (e.g.infoflattens a throwaway copy to read pixels). - Report reality. Export actions stat the files on disk after the run and return those paths/sizes — Aseprite occasionally renames sequence outputs.
- API choices validated against the binary, not guessed:
Frame.duration(notframeDuration),Color{index=},ColorQuantization{maxColors},json.encodemarker payloads, and flatten-to-new-spri 53A3 te for deterministic single-frame PNGs (baresaveAs('*.png')writes numbered sequences on multi-frame sprites). Validation scripts live inF:/ComfyUI/aseprite_output/scripts/.