{
  "markdown": "# aseprite-live-mcp\n\n[![PyPI](https://img.shields.io/pypi/v/aseprite-live-mcp)](https://pypi.org/project/aseprite-live-mcp/)\n[![Downloads](https://img.shields.io/pypi/dm/aseprite-live-mcp)](https://pypi.org/project/aseprite-live-mcp/)\n[![CI](https://img.shields.io/github/actions/workflow/status/oaktreegames/aseprite-live-mcp/ci.yml?label=CI)](https://github.com/oaktreegames/aseprite-live-mcp/actions/workflows/ci.yml)\n![license](https://img.shields.io/badge/license-MIT-green)\n![python](https://img.shields.io/badge/python-3.10%2B-blue)\n![MCP](https://img.shields.io/badge/MCP-tools%20·%20resources%20·%20prompts-8A2BE2)\n![tools](https://img.shields.io/badge/tools-139-orange)\n\n**Let an AI agent draw in your *live* Aseprite editor — every edit one named undo — with a Unity-first, game-dev toolset.**\n\nAn open-source [Model Context Protocol](https://modelcontextprotocol.io) server for\n[Aseprite](https://www.aseprite.org/). It drives your **running editor** (all open\ndocuments, unsaved tabs, in-editor dialogs, even asking *you* to click a point on\nthe canvas) *and* runs **headless** (`aseprite --batch`) against files on disk.\nOne-action **Unity sprite-sheet + `.meta` export** with per-slice pivots, a\nproject-convention **art-QA sweep**, **isometric primitives**, **tilemaps**, and a\nserver that **tells you when it's out of date**.\n\n## See it in action\n\nEach of these is an AI agent driving a **live** Aseprite canvas through the MCP — one named undo per step.\n\n<table>\n<tr>\n<td align=\"center\"><img src=\"docs/demo.gif\" width=\"240\" alt=\"Isometric machine build\"><br><sub><b>Isometric primitives</b><br>iso box → recessed port → tank → outline</sub></td>\n<td align=\"center\"><img src=\"docs/demo-drawing.gif\" width=\"240\" alt=\"Drawing suite\"><br><sub><b>Drawing suite</b><br>rect · dithered gradient · ellipse · circle · dither · outline</sub></td>\n<td align=\"center\"><img src=\"docs/demo-shading.gif\" width=\"240\" alt=\"Palette ramp-walk shading\"><br><sub><b>Ramp-walk shading</b><br>flat fill → volumetric, along a hand-ordered palette</sub></td>\n</tr>\n<tr>\n<td align=\"center\"><img src=\"docs/demo-tilemap.gif\" width=\"240\" alt=\"Tilemap editing\"><br><sub><b>Tilemaps</b><br>tileset drawn tile by tile → map laid out row by row</sub></td>\n<td align=\"center\"><img src=\"docs/demo-stamp.gif\" width=\"240\" alt=\"Stamp painting\"><br><sub><b>Stamp painting</b><br>one flower drawn once → scattered along an arc, one undo</sub></td>\n<td align=\"center\"><img src=\"docs/bridge-panel.png\" width=\"240\" alt=\"The Claude Bridge panel in Aseprite\"><br><sub><b>The bridge panel</b><br>live status, activity log, a Pause toggle — in Aseprite</sub></td>\n</tr>\n</table>\n\nAnd the part no headless server can do — the agent asking **you**, inside your editor\n(a real screen capture, not a mockup):\n\n<p align=\"center\"><img src=\"docs/dialog-in-editor.png\" width=\"560\" alt=\"An MCP-driven approval dialog inside Aseprite\"></p>\n\n## A full set, one live session\n\nEverything below came out of a single session against one running editor: five\nsprites built, QA'd, assembled into a sheet, sliced, and exported for Unity —\nfinished with an in-editor approval dialog. The `.aseprite` sources are in\n[`examples/showcase/`](examples/showcase/); open them and Ctrl-Z backwards\nthrough the build.\n\n<p align=\"center\"><img src=\"docs/showcase/showcase-sheet.png\" width=\"832\" alt=\"Assembled showcase sheet: iso building, tilemap, motion-QA renders, beacon pulse strip\"></p>\n\n<table>\n<tr>\n<td align=\"center\"><img src=\"docs/showcase/refinery.png\" width=\"256\" alt=\"Isometric refinery blockout\"><br><sub><b>Iso blockout, QA'd</b><br>face polygons → recessed port · ramp + palette-locked shading · rivets stamped in one undo · <code>qa_sweep</code> 4/4</sub></td>\n<td align=\"center\"><img src=\"docs/showcase/beacon-pulse.gif\" width=\"192\" alt=\"Beacon pulse animation\"><br><sub><b>Beacon pulse</b><br>additive glow · eased <code>tween_cel_opacity</code> loop · tagged + timed</sub></td>\n<td align=\"center\"><img src=\"docs/showcase/drone-dash.gif\" width=\"288\" alt=\"Drone dash animation\"><br><sub><b>Drone dash</b><br><code>tween_frames</code> smoothstep · <code>smear_frame</code> streak · endpoints verified with <code>frame_diff</code></sub></td>\n</tr>\n</table>\n\nThe QA tools aren't decoration. During this build, `frame_diff`'s change bbox\ncaught a motion tween the eye missed — every cel displaced by its image origin\nand clipping at the canvas edge. One transaction fixed it; the same diff\nre-verified it:\n\n<p align=\"center\"><img src=\"docs/showcase/drone-onion.png\" width=\"288\" alt=\"Onion-skin composite of the drone flight arc\"><br><sub><code>render_onion_composite</code> — earlier frames red, later frames blue, the whole arc in one image</sub></p>\n\nThe sheet's ten slices went to Unity in one action —\n[`examples/showcase/unity/`](examples/showcase/unity/) has the atlas PNG and its\ngenerated `.meta`: ten Multiple-mode sprites, rects y-flipped, every pivot baked.\n\n## Install\n\nOne command, no clone:\n\n```bash\nuvx aseprite-live-mcp        # or: pipx run aseprite-live-mcp\n```\n\nRequires **Python 3.10+** and **Aseprite 1.3+**. That's it for headless; the live\neditor needs one extra step (below).\n\n[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=aseprite-live&config=%7B%22type%22%3A%22stdio%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22aseprite-live-mcp%22%5D%7D)\n[![Install in Cursor](https://img.shields.io/badge/Cursor-Install_Server-000000?style=flat-square)](https://cursor.com/install-mcp?name=aseprite-live&config=eyJjb21tYW5kIjoidXZ4IGFzZXByaXRlLWxpdmUtbWNwIn0%3D)\n\n<details>\n<summary><b>Claude Code</b></summary>\n\n```bash\nclaude mcp add aseprite-live -- uvx aseprite-live-mcp\n```\n\n</details>\n\n<details>\n<summary><b>Claude Desktop / any JSON-config client</b></summary>\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"aseprite-live\": {\n      \"command\": \"uvx\",\n      \"args\": [\"aseprite-live-mcp\"],\n      \"env\": {\n        \"ASEPRITE_MCP_BACKEND\": \"headless\",           // or \"live\" — see below\n        \"ASEPRITE_PATH\": \"C:/Program Files/Aseprite/Aseprite.exe\"\n      }\n    }\n  }\n}\n```\n\n`ASEPRITE_PATH` is auto-detected on common installs (Steam and standalone) if you\nomit it.\n\n</details>\n\n<details>\n<summary><b>From source</b></summary>\n\n```bash\ngit clone https://github.com/oaktreegames/aseprite-live-mcp\ncd aseprite-live-mcp\npip install -e .\n```\n\n</details>\n\n## Quickstart — headless (2 minutes, no editor open)\n\nAdd the server with `\"ASEPRITE_MCP_BACKEND\": \"headless\"` (above). In headless\nmode, tools take a `file` argument — the `.aseprite` to act on. Ask your agent to\nrun `status` to confirm the connection, then try:\n\n> *\"Create a 32×32 sprite at C:/art/coin.aseprite, draw a gold circle, and export it as coin.png.\"*\n\n## Quickstart — live editor (drive your open canvas)\n\nThe live backend edits your **running** Aseprite, one undo step per edit. The\nbridge script and relay live in this repo (they're not in the pip package), so\ngrab them first: `git clone https://github.com/oaktreegames/aseprite-live-mcp`\n— or download just [`claude-bridge.lua`](aseprite_client/claude-bridge.lua) and\n[`relay_server.py`](bridge/relay_server.py).\n\n1. **Install the bridge script into Aseprite** — in Aseprite, `File → Scripts →\n   Open Scripts Folder`, copy `aseprite_client/claude-bridge.lua`\n   into it, and restart Aseprite (or `File → Scripts → Rescan Scripts Folder`).\n2. **Start the relay:** `python bridge/relay_server.py` (needs `pip install websockets`).\n3. **Connect:** in Aseprite, `File → Scripts → claude-bridge`, and grant the script\n   trust when asked. The panel shows **\"connected to Claude\"**, live activity, and a\n   Pause toggle.\n4. **Switch the backend** to `live` in your MCP config (`\"ASEPRITE_MCP_BACKEND\": \"live\"`).\n\nNow tools act on your active document; watch edits appear and press Ctrl-Z to undo\neach one. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for how it works.\n\n## What you can ask it to do\n\n- *\"Make a 32×32 sprite, draw a red circle, and export it as icon.png.\"*\n- *\"Export machines.aseprite as a Unity sprite sheet at 16 pixels-per-unit.\"* → drops a sheet + a pre-sliced `.meta` straight into your Unity project.\n- *\"Slice this atlas, set each slice's pivot, and export it to Unity with the pivots baked in.\"*\n- *\"Run a QA sweep: it must be 64×64, ≤ 16 colors, grid-aligned, with 2px of sticker padding.\"*\n- *\"Draw an isometric box for a 1×1 machine base and put a recessed port on its front face.\"*\n- *\"Set up a 16×16 tilemap layer, draw grass and dirt tiles, and lay out a 10×6 ground strip.\"*\n- *\"Assemble my exports into one labeled sheet, slice each piece, set the pivots, and hand Unity the atlas.\"*\n- *\"Pop a dialog in my editor: approve or redo. Then wait for me to click the anchor point.\"*\n- *\"Watch the sprite while I touch it up; when I stop, re-run the QA sweep.\"*\n- *\"Diff frames 3 and 4 — did the arm actually move?\"*\n\n## Where this sits (honest positioning)\n\nThere are already good Aseprite MCP servers. This project is **not the first**,\nand doesn't claim to be. What it claims — and can defend:\n\n| Capability | Landscape | This project |\n|---|---|---|\n| Draw / layers / frames / palette / selection / export, dithering, gradients, outlines, filters, tweens, tilemaps | Well covered by the headless leaders (diivi ~113 tools, pixel-mcp, ext-sakamoro) and the commercial live server (Aseprite MCP Pro, Godot-first) | **Parity** — a curated, fully typed surface |\n| **One-action Unity export** — sheet + `.meta` with per-slice pivots baked in | Every other Aseprite→Unity path we found is a separate engine-side importer; none export pivots | **Differentiator** ✅ |\n| **Art-QA sweep** incl. transparent sticker-padding measurement against your project conventions | We found no other server that lints sprite margins/padding as a packaged sweep | **Differentiator** ✅ |\n| **Isometric drawing primitives** (2:1 boxes/cylinders, auto face-shading, recessed panels) | The only iso primitives we've found in any Aseprite MCP we surveyed | **Differentiator** ✅ |\n| **In-editor interaction**: dialogs in Aseprite, ask-the-artist point picking, artist-edit watching, symmetry & viewport control | Live-GUI servers exist (Pro commercial; two nascent OSS); none we surveyed ship in-editor dialogs or human-in-the-loop tools | **Differentiator** ✅ |\n| **Update awareness** — the server itself tells you when a newer release exists | As far as we could find (checked 2026-07-16), no other MCP server surfaces its own update status through the session | **Differentiator** ✅ |\n| **Live tilemap editing** | The headless leaders edit tilemaps on disk; the only live tilemap editing we found is closed-source | Open-source position ✅ |\n| MCP resources + prompts | Across the open-source Aseprite MCPs we surveyed, this is the richest MCP-native surface (live-state resources + guided-workflow prompts) | Differentiator (hedged) |\n\nEvery edit made **through the server** is exactly one *named* undo step\n(`app.transaction`) — you can watch an agent work and Ctrl-Z anything it did,\none step at a time. We verified the other open-source live bridges don't hold\nthat discipline across their surface (one wraps 3 of 57 tools).\n\n## Knowing when to update\n\nThe `status` tool reports the server's version and — at most once a day, via\nPyPI — whether a newer release exists. Your agent sees it, so *you* see it:\n\n```json\n{ \"backend\": \"live\", \"connected\": true,\n  \"server\": { \"current\": \"0.2.0\", \"latest\": \"0.2.1\", \"update_available\": true,\n              \"how\": \"pip install -U aseprite-live-mcp (uvx users: run uvx aseprite-live-mcp@latest once — uvx reuses its cached version otherwise)\" } }\n```\n\nAlso surfaced as the `aseprite://update` resource, one stderr log line, and the\nreal version in the MCP `serverInfo` handshake. Disable with\n`ASEPRITE_MCP_NO_UPDATE_CHECK=1` (auto-disabled in CI). No telemetry — the check\nis an HTTP GET of PyPI's public JSON; nothing about you, your machine, or your\nart is sent.\n\n<!-- TOOLS:START -->\n## Tools (139)\n\nEvery tool is typed, annotated (`readOnlyHint`/`destructiveHint`/`idempotentHint`/`openWorldHint`), and — when it mutates — wrapped in a **named** `app.transaction`, so each edit is exactly one labeled undo step in the editor.\n\n<details>\n<summary><b>Inspect & metadata</b> (6)</summary>\n\n- **`status`** — Report backend health plus this server's version and whether a newer release exists.\n- **`get_sprite_info`** — Get the active sprite's size, color mode, and layer/frame/tag lists.\n- **`get_pixel`** — Read one composited pixel (all layers) at (x, y) as #rrggbbaa.\n- **`set_sprite_metadata`** — Attach typed key/value metadata to the sprite (Aseprite's Properties system — stored in the .aseprite file itself).\n- **`get_sprite_metadata`** — Read the sprite's typed key/value metadata (Properties).\n- **`get_pixels`** — Read a composited pixel region as a grid of #rrggbbaa strings — the AI feedback loop for 'what does the canvas look like now'.\n\n</details>\n<details>\n<summary><b>Documents & session</b> (5)</summary>\n\n- **`open_sprite`** — Open a sprite file and make it the active document; returns its filename, size, and frame/layer counts.\n- **`save_sprite`** — Save the active sprite to disk: to ``path`` if given (save-as; the document's filename follows it), else to its own filename — errors if the sprite is untitled and no ``path`` is given.\n- **`close_sprite`** — Close the active sprite.\n- **`set_active_sprite`** — Live editor only: make one of the open documents active, found by exact filename or basename match (e.g. \"turret.aseprite\").\n- **`list_sprites`** — Live editor only: list every open document — filename (or \"(unsaved)\"), size, frame/layer counts, unsaved-changes flag, and which one is active.\n\n</details>\n<details>\n<summary><b>Canvas</b> (3)</summary>\n\n- **`create_sprite`** — Create a new sprite.\n- **`expand_canvas`** — Grow (or shrink, with negatives) the canvas per side WITHOUT scaling content — existing pixels keep their positions relative to the old origin.\n- **`import_spritesheet`** — Slice a sheet PNG into animation frames: cells are read row-major at the given frame size onto a new \"imported\" layer, adding frames as needed.\n\n</details>\n<details>\n<summary><b>Layers & groups</b> (14)</summary>\n\n- **`add_layer`** — Add a new empty layer (optionally named) at the top of the stack.\n- **`add_group`** — Add a layer group.\n- **`move_layer_to_group`** — Move a layer inside a layer group.\n- **`list_layers`** — The full layer tree, top of stack first: name, path, kind (image/group/tilemap), visibility, and opacity — including layers nested in groups (which ``get_sprite_info`` lists only at the top level).\n- **`delete_layer`** — Delete the layer with the given name.\n- **`rename_layer`** — Rename a layer.\n- **`set_layer_visibility`** — Show or hide a layer.\n- **`set_layer_opacity`** — Set a layer's opacity (0-255).\n- **`flatten_sprite`** — Flatten all layers into a single layer.\n- **`set_layer_blend_mode`** — Set a layer's blend mode.\n- **`duplicate_layer`** — Duplicate a layer (with its cels) above the original.\n- **`merge_layer_down`** — Merge a layer into the layer directly beneath it.\n- **`move_layer`** — Reorder a layer in the stack.\n- **`import_image_as_layer`** — Import an external image file (PNG, etc.) into a new layer at (x, y) — a reference/trace layer for concept-art intake.\n\n</details>\n<details>\n<summary><b>Frames</b> (8)</summary>\n\n- **`add_frame`** — Append a new empty frame.\n- **`duplicate_frame`** — Duplicate frame ``number`` (1-based).\n- **`delete_frame`** — Delete frame ``number`` (1-based).\n- **`set_frame_duration`** — Set frame ``number``'s duration in milliseconds.\n- **`add_frames`** — Append ``count`` empty frames in one undo step.\n- **`set_frame_durations`** — Set every frame in [from_frame..to_frame] to ``ms`` milliseconds.\n- **`reverse_frames`** — Reverse frame order (cels on every layer, plus durations) — the whole sprite by default, or the inclusive [from_frame..to_frame] range.\n- **`set_onion_skin`** — Configure the editor's onion-skin view: on/off, ghost counts each direction, and whether ghosting stays inside the current tag.\n\n</details>\n<details>\n<summary><b>Cels</b> (3)</summary>\n\n- **`clear_cel`** — Empty the cel at the given layer + frame (transparent).\n- **`set_cel_position`** — Move the cel's top-left origin to (x, y).\n- **`set_cel_opacity`** — Set the cel's opacity (0-255).\n\n</details>\n<details>\n<summary><b>Drawing</b> (10)</summary>\n\n- **`draw_pixels`** — Set individual pixels: ``pixels`` is a list of ``{\"x\": int, \"y\": int, \"color\": \"#rrggbb[aa]\"}``.\n- **`draw_polyline`** — Stroke connected line segments through ``points`` (ordered [x, y] pairs); ``close=True`` joins the last point back to the first.\n- **`draw_rectangle`** — Draw a rectangle on the active layer + frame.\n- **`draw_line`** — Draw a 1px line from (x1,y1) to (x2,y2).\n- **`draw_circle`** — Draw a circle centered at (center_x, center_y).\n- **`fill_area`** — Flood-fill the contiguous same-color region at (x, y).\n- **`draw_ellipse`** — Draw an ellipse centered at (center_x, center_y) with independent x/y radii.\n- **`draw_gradient`** — Fill a rectangle with a linear gradient from ``color1`` to ``color2``.\n- **`apply_dither`** — Fill a rectangle with a uniform ordered (Bayer 4x4) dither mixing ``color1`` and ``color2``.\n- **`add_outline`** — Add a 1px outline in ``color`` around the opaque pixels of the active cel — readability against busy backgrounds.\n\n</details>\n<details>\n<summary><b>Palette</b> (12)</summary>\n\n- **`get_palette`** — Return the active sprite's palette as #rrggbbaa hex strings.\n- **`set_palette_color`** — Set palette entry ``index`` to ``color`` (#rrggbb or #rrggbbaa).\n- **`add_palette_color`** — Append ``color`` to the palette and return its new index.\n- **`set_palette`** — Replace the whole palette with ``colors`` (list of #rrggbb/#rrggbbaa).\n- **`apply_preset_palette`** — Replace the palette with a built-in preset: gameboy | pico8 | cga.\n- **`generate_color_ramp`** — Generate a dark->light shading ramp from ``base_color`` and APPEND it to the palette in order — ready for ``palette_ramp_walk``.\n- **`extract_palette_from_image`** — Extract a palette (median-cut) from an external image file and, by default, set it as the active sprite's palette — the concept-art → locked-palette step.\n- **`sort_palette`** — Reorder palette swatches by ``luminance`` (default), ``hue``, or ``saturation``.\n- **`palette_from_sprite`** — Derive the palette FROM the sprite's actual pixels (Aseprite's color quantization) — the inverse of ``quantize_to_palette``.\n- **`load_palette`** — Replace the sprite's palette from a palette file (.gpl, .pal, .ase, .png strip — anything Aseprite reads).\n- **`save_palette`** — Write the sprite's palette to a file (.gpl, .pal, .png — by extension).\n- **`apply_bundled_palette`** — Apply one of Aseprite's bundled extension palettes by resource ID — e.g. DB16, DB32, ARQ4, EDG16 (availability depends on the installed palette extensions).\n\n</details>\n<details>\n<summary><b>Color</b> (4)</summary>\n\n- **`replace_color`** — Replace every pixel matching ``from_color`` (within ``tolerance``, a 0-255 RGB distance) with ``to_color`` across the active cel, preserving each pixel's alpha unless ``to_color`` carries its own.\n- **`quantize_to_palette`** — Snap every opaque pixel of the active cel to its nearest color in the sprite's palette (alpha preserved) — enforce palette discipline after intake or shading.\n- **`adjust_hsl`** — Shift the active cel's colors in HSL: ``hue_shift`` degrees (-360..360), ``saturation`` a multiplier (1.0 = unchanged), ``lightness`` a delta (-1..1).\n- **`get_color_stats`** — Return a color histogram of the composited sprite: distinct opaque color count, total opaque pixels, and the ``top`` most-used colors with counts — a palette-drift check for the art-QA loop.\n\n</details>\n<details>\n<summary><b>Shading</b> (2)</summary>\n\n- **`apply_shading`** — Palette-constrained shading: brighten toward the light and darken away from it, snapping every pixel to the nearest palette color.\n- **`palette_ramp_walk`** — Shade by WALKING the arranged palette ±N indices toward lighter/darker (per light direction), instead of snapping to the luminance-nearest color — so hand-ordered shading ramps are respected.\n\n</details>\n<details>\n<summary><b>Filters</b> (5)</summary>\n\n- **`adjust_brightness_contrast`** — Adjust the active cel's brightness and contrast, each -100..100 (0 = unchanged).\n- **`invert_colors`** — Invert the active cel's RGB (alpha untouched).\n- **`desaturate`** — Convert the active cel to grayscale (Rec. 601 luma), keeping alpha.\n- **`box_blur`** — Blur the active cel with an alpha-weighted box kernel.\n- **`sharpen`** — Sharpen the active cel (unsharp mask over a 3x3 neighborhood).\n\n</details>\n<details>\n<summary><b>Stamp painting</b> (1)</summary>\n\n- **`paint_with_stamp`** — Stamp an image at each [x, y] in ``points`` (points are stamp centers).\n\n</details>\n<details>\n<summary><b>Isometric</b> (4)</summary>\n\n- **`draw_iso_box`** — Draw a 2:1 isometric box.\n- **`draw_iso_cylinder`** — Draw a vertical isometric cylinder (tank/silo/chimney): top ellipse + shaded body + front rim, auto-shaded from ``color``.\n- **`fill_polygon`** — Scanline-fill an arbitrary polygon given ``points`` (a list of [x, y] pairs, in order).\n- **`iso_recessed_panel`** — Draw a recessed docking socket/port on an isometric face — a light lit lip framing a dark recessed interior.\n\n</details>\n<details>\n<summary><b>Selection</b> (8)</summary>\n\n- **`select_rectangle`** — Set a rectangular selection.\n- **`select_all`** — Select the whole canvas.\n- **`deselect`** — Clear the current selection.\n- **`select_ellipse`** — Select an ellipse inscribed in the given rectangle.\n- **`select_by_color`** — Select every pixel matching ``color`` (composited, within ``tolerance`` 0-255 per channel) — a global magic wand.\n- **`invert_selection`** — Invert the current selection against the canvas bounds.\n- **`get_selection`** — Report whether a selection exists and its bounding rectangle.\n- **`erase_area`** — Erase pixels to transparent.\n\n</details>\n<details>\n<summary><b>Transform</b> (6)</summary>\n\n- **`flip_sprite`** — Flip the whole sprite horizontally or vertically.\n- **`resize_sprite`** — Scale the whole sprite to new pixel dimensions.\n- **`crop_sprite`** — Crop the sprite to the given rectangle.\n- **`rotate_sprite`** — Rotate the whole sprite by 90, 180, or 270 degrees clockwise (lossless).\n- **`autocrop_sprite`** — Trim transparent borders: shrink the canvas to the union of visible content across all layers and frames (Sprite > Trim).\n- **`set_color_mode`** — Convert the sprite between rgb | gray | indexed color modes.\n\n</details>\n<details>\n<summary><b>Slices</b> (5)</summary>\n\n- **`create_slice`** — Create a named slice (atlas region) at the given rectangle.\n- **`set_slice_pivot`** — Set a slice's pivot point, in pixels relative to the slice's top-left.\n- **`set_slice_center`** — Set a slice's 9-patch stretchable center rectangle (relative to the slice).\n- **`list_slices`** — List every slice with its bounds, pivot, and 9-patch center.\n- **`delete_slice`** — Delete the slice with the given name.\n\n</details>\n<details>\n<summary><b>Tags</b> (7)</summary>\n\n- **`create_tag`** — Create an animation tag named ``name`` spanning frames ``from_frame``..``to_frame`` (1-based).\n- **`delete_tag`** — Delete the animation tag named ``name``.\n- **`list_tags`** — List animation tags with their frame ranges.\n- **`rename_tag`** — Rename the animation tag ``old_name`` to ``new_name``.\n- **`set_tag_color`** — Set the timeline label color of the tag ``name`` (#rrggbb or #rrggbbaa).\n- **`set_tag_range`** — Move the tag ``name`` to span frames ``from_frame``..``to_frame`` (1-based, inclusive; validated against the sprite's frame count).\n- **`set_tag_direction`** — Set the playback direction of the tag ``name``: forward | reverse | pingpong | pingpong_reverse.\n\n</details>\n<details>\n<summary><b>Tilemaps</b> (6)</summary>\n\n- **`create_tilemap_layer`** — Create a tilemap layer with its own tileset on a tile_width x tile_height grid.\n- **`add_tile`** — Append an empty tile to a tilemap layer's tileset; returns the new tile's index and the tileset size.\n- **`draw_on_tile`** — Set pixels on one tile of a tilemap layer's tileset.\n- **`set_tiles`** — Place tile references on a tilemap layer's cel.\n- **`get_tilemap_info`** — Read a tilemap layer: tile size, tileset length, and the cel's grid size, pixel position, and tile-index grid (``tiles`` as rows of columns, flip flags stripped; capped at 64x64 with ``truncated`` set).\n- **`export_tileset`** — Export a tilemap layer's tileset as a horizontal PNG strip (plus an Aseprite JSON atlas if ``data_path`` is given).\n\n</details>\n<details>\n<summary><b>Animation</b> (5)</summary>\n\n- **`tween_frames`** — Insert ``count`` in-between frames between ``from_frame`` and ``to_frame`` that interpolate the cel's position (a motion tween of the start cel's image).\n- **`tween_cel_opacity`** — Fade a layer's cels across [from_frame..to_frame]: opacity interpolates from ``from_opacity`` to ``to_opacity`` (0-255) with the chosen easing.\n- **`smear_frame`** — Append a motion-smear frame blending ``from_frame`` and ``to_frame``: overlapping pixels averaged, single-frame areas ghosted at half alpha — a pixel-content smear.\n- **`frame_diff`** — Pixel-exact diff of two composited frames: changed-pixel count and ratio, the bounding box of the change, and whether they're identical.\n- **`render_onion_composite`** — Write a PNG of ``frame`` with up to ``prev`` earlier frames ghosted in red and ``next`` later frames ghosted in blue (Aseprite's onion-skin convention) at the given ghost ``opacity`` — so you can *see* the motion arc in one image.\n\n</details>\n<details>\n<summary><b>Export</b> (4)</summary>\n\n- **`export_sprite`** — Export the active sprite to a file; format is chosen by ``out_path`` extension (.png, .gif, ...).\n- **`export_spritesheet`** — Pack all frames into a single sprite-sheet image (optionally writing a JSON atlas to ``data_path``).\n- **`export_layers`** — Export every image layer (recursing into groups) as its own PNG in ``out_dir``, named ``<sanitized-layer-name>.png``.\n- **`export_tags`** — Export each animation tag's frame range as its own file in ``out_dir``: an animated ``<tag-name>.gif`` (format=\"gif\", default) or a horizontal ``<tag-name>.png`` strip (format=\"png\").\n\n</details>\n<details>\n<summary><b>Unity export</b> (2)</summary>\n\n- **`export_unity_sprite`** — Export a horizontal sprite sheet AND write the Unity ``.meta`` beside it, pre-sliced into named Multiple-mode sprites (one per frame) — one action, no manual Sprite Editor pass.\n- **`export_unity_slices`** — Export the sprite as a single PNG AND write a Unity ``.meta`` with one Multiple-mode sprite **per named slice**, each carrying that slice's pivot — so an atlas laid out with slices imports into Unity already sliced *and* pivoted.\n\n</details>\n<details>\n<summary><b>Art QA</b> (2)</summary>\n\n- **`measure_padding`** — Measure the transparent 'sticker' padding around the content: content bounding box, per-side padding (left/right/top/bottom), fill ratio, and distinct opaque color count.\n- **`qa_sweep`** — Audit the active sprite against project conventions and return per-check pass/fail.\n\n</details>\n<details>\n<summary><b>Batch (whole folder)</b> (3)</summary>\n\n- **`batch_export`** — Export every ``.aseprite`` in ``folder`` (first frame) to an image, optionally integer-upscaled by ``scale``.\n- **`batch_resize`** — Resize every ``.aseprite`` in ``folder`` (first frame, nearest-neighbor) and write images to ``out_dir``.\n- **`batch_apply_palette`** — Palette-lock every ``.aseprite`` in ``folder``: snap each sprite (first frame, alpha preserved) to a palette and export images to ``out_dir``.\n\n</details>\n<details>\n<summary><b>Undo / redo</b> (2)</summary>\n\n- **`undo`** — Undo the last edit on the active sprite (live editor).\n- **`redo`** — Redo the last undone edit (live editor).\n\n</details>\n<details>\n<summary><b>Live editor (interactive)</b> (11)</summary>\n\n- **`get_editor_state`** — Live editor only: current zoom, scroll, and which sprite pixel the mouse is over — lets an agent know what the artist is looking at.\n- **`set_editor_view`** — Live editor only: set the viewport zoom (1.0 = 100%) and/or scroll — e.g. zoom to the region you're about to edit so the artist can watch.\n- **`set_symmetry`** — Live editor only: set mirror-painting symmetry — ``mode`` is ``none``, ``horizontal`` (mirror across a vertical axis), ``vertical``, or ``both``; optionally place the axis lines.\n- **`ask_point`** — Live editor only: ask the ARTIST to click a point on the canvas (pivot, anchor, paint origin).\n- **`get_asked_point`** — Poll the result of ``ask_point``: ``done`` false while waiting, then the clicked ``x``/``y`` (or ``cancelled`` if the artist pressed Esc).\n- **`cancel_ask_point`** — Withdraw a pending ``ask_point`` request (e.g. the artist stepped away and you're moving on).\n- **`show_choice_dialog`** — Live editor only: pop a small dialog IN Aseprite with a message and up to 4 option buttons (e.g. approve/redo).\n- **`get_dialog_choice`** — Poll the result of ``show_choice_dialog``: ``done`` false while the dialog is open, then the chosen button text.\n- **`watch_changes`** — Live editor only: start counting edits the ARTIST makes to the active sprite (via ``Sprite.events``).\n- **`get_watched_changes`** — Poll the change counter started by ``watch_changes`` (``reset=True`` zeroes it after reading).\n- **`stop_watching_changes`** — Detach the ``watch_changes`` listener.\n\n</details>\n<details>\n<summary><b>Advanced</b> (1)</summary>\n\n- **`run_lua`** — Run arbitrary Lua inside Aseprite and return its result (advanced escape hatch — equivalent to local code execution).\n\n</details>\n<!-- TOOLS:END -->\n\n## Resources\n\nRead editor state without spending a tool call:\n\n- `aseprite://sprite/active` — size, color mode, layer/frame/tag names of the active document\n- `aseprite://palette/active` — the active palette as hex\n- `aseprite://docs` — every open document, including unsaved/untitled tabs (live)\n- `aseprite://qa/last` — the most recent `qa_sweep` / `measure_padding` result\n- `aseprite://export/last` — the most recent Unity export (files written + sprite manifest)\n- `aseprite://update` — this server's version + whether a newer release exists\n\n## Prompts (guided workflows)\n\nOne-call \"skills\" that chain the tools into finished art tasks:\n\n- `unity_qa_gate` — run the ship-ready QA gate, then export a Unity sheet + `.meta`\n- `iso_building_to_spec` — build a 2:1 isometric building from a footprint spec\n- `shade_along_ramp` — shade a sprite by walking its hand-ordered palette ramp\n- `autoslice_to_unity` — multi-frame sprite → Unity-import-ready sheet + pre-sliced `.meta`\n- `concept_to_placeholder` — external concept image → correctly-padded, in-palette placeholder\n- `tileset_and_map` — author a tileset from a terrain spec, lay out the map, export the strip\n- `live_review_loop` — wait for the artist to finish touching up, re-QA, ask for the ship call in-editor\n\n## Limitations (honest ones)\n\n- **Headless calls are separate Aseprite processes** — editor-session state\n  (selection, view, symmetry) doesn't persist between headless calls. Pixels,\n  layers, frames, palettes, tilemaps, metadata: all persist (they're in the file).\n- **The interactive tools need the live editor** — dialogs, point picking,\n  watching, viewport control error cleanly under `headless`.\n- **Animated `.gif` export is headless-only.** Saving `.gif` from the live\n  editor opens Aseprite's GIF-options dialog, which blocks the bridge until\n  dismissed. Live, use `export_spritesheet` strips or per-frame PNGs; the\n  animated GIFs in this README were exported headless from the same files.\n- **Windows is the primary dev platform.** CI runs the unit + wire-validation\n  suite on Linux/macOS/Windows, but CI has no Aseprite binary — anything that\n  touches a real Aseprite (headless batch or the live bridge) is hand-tested on\n  Windows. Treat mac/linux as expected-to-work, not proven.\n- **`run_lua` is local code execution** by design (it's the escape hatch). The\n  relay binds to `127.0.0.1` only. Read [`SECURITY.md`](SECURITY.md).\n- **No generative art.** This server draws what it's told with deterministic\n  primitives; taste stays with the artist.\n\n## Verifying the surface\n\n`python tests/smoke_headless.py` drives every tool (plus resources + prompts)\nin-process against a throwaway sprite. `python -m pytest` runs the unit suite\nplus **wire-validation** tests that exercise structured output through a real\nMCP client session — the boundary users actually hit (`pip install -e \".[dev]\"`\nfirst). The README's tool list is generated from the code\n(`python scripts/gen_docs.py`) and CI fails if it drifts.\n\n## Changelog & releases\n\nSee [`CHANGELOG.md`](CHANGELOG.md) — every release also lands as a GitHub Release\nwith the same notes. Versions follow semver (pre-1.0: minor bumps may change\ntool behavior, patches are fixes).\n\n## About\n\nBuilt solo by [Kyle Ocheltree](https://github.com/oaktreegames) (OakTree Games)\nfor a real production pipeline — the Unity export, padding QA, and isometric\ntools exist because Cosmic Scale (an isometric factory game) needed\nthem. Developed with an AI-native workflow: AI assists the coding; every tool\nis reviewed, tested against a real editor, and shipped deliberately.\n\n## Contributing\n\nIssues and PRs welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md). Run\n`ruff check .`, `pytest`, and `python tests/smoke_headless.py` before submitting.\n\n## License\n\nMIT — see [`LICENSE`](LICENSE). © 2026 Kyle Ocheltree / OakTree Games.\n\n<!-- mcp-name: io.github.oaktreegames/aseprite-live-mcp -->\n<!-- The line above is the MCP Registry PyPI ownership marker (ships in the PyPI\n     description). It must match the \"name\" in server.json. Do not remove. -->\n",
  "bytes": 34035,
  "sha": "ef94ae622c63f0ac191d9d05ce9fdb2ada82269cb37bb90e76de5c56306e112e",
  "repo_slug": "oaktreegames/aseprite-live-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_oaktreegames_aseprite_live_mcp_7cdbcdac/readme"
}