Skip to main content
Glama
Mnehmos

mnehmos.aseprite.mcp

by Mnehmos
README.md
# mnehmos.aseprite.mcp

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.

## Tools

| 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](https://github.com/aseprite/api)) |

### Safety model

- **Serialized projects**: calls that touch the same `.aseprite` run 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.bak` first; the response reports the `backup` path. 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_script` is 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.

### The ASCII pixel-map (why `map` exists)

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.

## Layout (canonical, env-overridable)

- 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
  ComfyUI `LoadImage` nodes)
- Generated scripts: `F:/ComfyUI/aseprite_output/mcp-scripts/` (kept for
  debugging)
- Audit/provenance DB: `data/aseprite-mcp.db`

## Setup

```
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`):

```json
"aseprite": {
  "command": "<node>",
  "args": ["F:\\Github\\mnehmos.aseprite.mcp\\dist\\index.js"],
  "env": { "ASEPRITE_BIN": "F:\\ComfyUI\\tools\\aseprite\\aseprite.exe" },
  "enabled": true
}
```

## Environment

| 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` |

## Design notes

- **Stateless runs, durable state.** Each call loads the `.aseprite` fresh,
  mutates in memory, saves explicitly. Destructive Aseprite commands are safe
  on the in-memory copy (e.g. `info` flattens 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` (not `frameDuration`), `Color{index=}`,
  `ColorQuantization{maxColors}`, `json.encode` marker payloads, and
  flatten-to-new-sprite for deterministic single-frame PNGs (bare
  `saveAs('*.png')` writes numbered sequences on multi-frame sprites).
  Validation scripts live in `F:/ComfyUI/aseprite_output/scripts/`.

Maintenance

ActivityMaintained
ResponsivenessNo issues