Metadata-Version: 2.5
Name: aseprite-agent-mcp
Version: 0.3.0
Summary: An extensive Model Context Protocol (MCP) server for driving Aseprite (pixel-art editor) headlessly via Lua scripting and the Aseprite CLI.
Project-URL: Homepage, https://github.com/SarutobiSasuke8/aseprite-agent-mcp
Project-URL: Repository, https://github.com/SarutobiSasuke8/aseprite-agent-mcp
Project-URL: Issues, https://github.com/SarutobiSasuke8/aseprite-agent-mcp/issues
Project-URL: Changelog, https://github.com/SarutobiSasuke8/aseprite-agent-mcp/blob/main/CHANGELOG.md
Author: Aseprite Agent MCP contributors
License-Expression: MIT
License-File: LICENSE
License-File: NOTICE
Keywords: animation,aseprite,mcp,model-context-protocol,pixel-art,sprite
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics :: Editors
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: mcp<2,>=1.4.0
Requires-Dist: pillow>=12.2.0
Description-Content-Type: text/markdown

# Aseprite Agent MCP

[![CI](https://github.com/SarutobiSasuke8/aseprite-agent-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/SarutobiSasuke8/aseprite-agent-mcp/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/aseprite-agent-mcp)](https://pypi.org/project/aseprite-agent-mcp/)
[![Python](https://img.shields.io/pypi/pyversions/aseprite-agent-mcp)](https://pypi.org/project/aseprite-agent-mcp/)
[![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)

<!-- mcp-name: io.github.SarutobiSasuke8/aseprite-agent-mcp -->

Local-first pixel-art authoring and verified game-asset export over MCP stdio.

**Status: 0.2.0 alpha.** Built on [aseprite-mcp](https://github.com/MalloyTheDev/aseprite-mcp)
by Brendan Malloy (MIT), pinned to `8caabf9ce240a040ca2c94d3d7366c9d8dd63653`.
The added production layer provides atomic edits with previews, strict asset contracts,
verified Phaser export, tool profiles, cast tools, workspace locking and crash-safe saves.
See [NOTICE](NOTICE),
[LICENSE](LICENSE) and [UPSTREAM_INVENTORY.json](UPSTREAM_INVENTORY.json).
The inherited Python namespace remains `aseprite_mcp`; the package and command are `aseprite-agent-mcp`.

114 tools cover native documents, pixel drawing, layers, cels, frames, palettes,
tags, slices, transforms, tilemaps, inspection and exports. The production path
adds atomic edits with image feedback, strict typed asset contracts, labelled
all-frame previews, character-cast tools and Phaser bundles verified against
reopened native masters. By default the server exposes a 42-tool `core` profile
(see [Tool profiles](#tool-profiles)). See the generated
[tool reference](docs/TOOLS.md) for exact schemas.

## Install and run

Install Python 3.10+, uv or pipx, and your own licensed Aseprite with Lua scripting
support. You may also build Aseprite yourself under its EULA. This package only
invokes your installed executable via `ASEPRITE_PATH`; it does not bundle Aseprite,
its installer, Lua runtime or art assets.

For the published package:

```sh
uvx aseprite-agent-mcp
# or: pipx install aseprite-agent-mcp
# then: aseprite-agent-mcp
```

Set `ASEPRITE_PATH` before running. Example MCP client commands (replace the paths):

```sh
# Windows (PowerShell)
claude mcp add aseprite --env ASEPRITE_PATH=C:\\path\\to\\Aseprite.exe -- uvx aseprite-agent-mcp
# macOS
claude mcp add aseprite --env ASEPRITE_PATH=/Applications/Aseprite.app/Contents/MacOS/aseprite -- uvx aseprite-agent-mcp
# Linux
claude mcp add aseprite --env ASEPRITE_PATH=/path/to/aseprite -- uvx aseprite-agent-mcp
```

For clients that take JSON (including Codex MCP configuration), use this shape:

```json
{
  "mcpServers": {
    "aseprite": {
      "command": "uvx",
      "args": ["aseprite-agent-mcp"],
      "env": {
        "ASEPRITE_PATH": "/path/to/aseprite",
        "ASEPRITE_MCP_WORKSPACE": "/path/to/assets"
      }
    }
  }
}
```

Grok clients that support local stdio MCP servers can use the same executable,
arguments and environment fields. Client-specific configuration keys may vary.

For a source checkout and development:

```powershell
uv sync --locked --group dev
$env:ASEPRITE_PATH = 'C:\path\to\aseprite.exe'
$env:ASEPRITE_MCP_WORKSPACE = 'C:\path\to\assets'
uv run --no-sync aseprite-agent-mcp
```

The command speaks MCP on stdin/stdout and waits for a client. Diagnostic logs go
to stderr. Use [mcp-config.example.json](mcp-config.example.json) in a client after
replacing its placeholder paths. This project does not modify client settings.

![Procedural four-frame robot preview](docs/demo/synthetic-robot.gif)

The small GIF is procedural example art, generated by
`uv run python scripts/generate_demo.py`. It demonstrates the intended pixel-art
output; it is not a screenshot of a live Aseprite session. A full Aseprite MCP
round-trip proof is described under [Verification](#verification).

## Tool profiles

Every tool description and schema costs the agent context, so the server exposes a
profile chosen by `ASEPRITE_MCP_TOOLS`, a comma-separated list of names:

| Value | Exposes |
| --- | --- |
| `core` (default) | 42 tools: generate/import, create, draw, animate, preview, validate, export and the cast tools |
| `all` | Every supported tool (114) |
| a group | One tool module, e.g. `tilemap`, `effects`, `slices`, `palette`, `workflow` |
| a tool name | One extra tool, e.g. `core,add_outline` |

Unknown names stop the server at startup. The groups are listed at the top of the
[tool reference](docs/TOOLS.md).

| Variable | Default | Meaning |
| --- | --- | --- |
| `ASEPRITE_PATH` | auto-detect | Aseprite executable |
| `ASEPRITE_MCP_WORKSPACE` | `workspace/` in the project | Root for relative paths |
| `ASEPRITE_MCP_TOOLS` | `core` | Tool profile, as above |
| `ASEPRITE_MCP_LOCK_WAIT` | `15` | Seconds a call waits for a busy workspace (0 fails at once) |
| `ASEPRITE_MCP_TIMEOUT` | `90` | Seconds per Aseprite process |
| `ASEPRITE_MCP_ALLOW_ABSOLUTE` | off | Allow absolute path syntax inside allowed roots |
| `ASEPRITE_AGENT_ALLOWED_ROOTS` | `[]` | JSON list of extra roots |

## Recommended workflow

### Generated art to editable animation

Let an image model draw the character and key poses, then use `import_generated_image`
to turn one PNG into a pixel grid and `assemble_animation` to clean and align an
ordered pose list. Both tools are in the default `core` profile.

```python
import_generated_image(filename="base.png", output_directory="base-clean",
                       width=32, height=32, colours=16)
assemble_animation(filenames=["idle-1.png", "idle-2.png", "idle-3.png", "idle-4.png"],
                   output_directory="idle-clean", width=32, height=48, tag="idle",
                   colours=16, alignment="bounding-box",
                   durations_ms=[180, 140, 180, 140], outline="#152b32")
```

- `method="nearest"` samples a grid; `"majority"` chooses the most frequent RGBA
  value in each source cell, with deterministic ties. Majority only downsamples.
- `width` and `height` accept grids up to 256px per edge. For detailed sprites,
  use `height=128`, `colours=32` to `48`, `method="majority"` and
  `palette_first=True`. Palette-first mode snaps one shared source palette before
  voting, so near-identical generated shades count together. It is opt-in;
  existing calls keep their previous behaviour.
- `palette=["#RRGGBB", ...]` snaps to explicit colours. Otherwise `colours` builds
  one shared automatic palette across the animation. Alpha is binary after
  `alpha_threshold` (default 128). Transparency is excluded from the colour count;
  an optional 1px outline can add one colour and is clipped at the cell boundary.
- `background="#RRGGBB"` removes only border-connected matching pixels, with an
  optional `background_tolerance`. It preserves enclosed details and is a colour
  key, not semantic segmentation. Existing transparency needs no background key.
- Bounding-box alignment uses one scale for all poses and bottom-centres each
  silhouette. `alignment="anchor"` accepts one source `[x,y]` per same-size input
  and places it at `(width//2,height-2)`, rejecting clipping. `"none"` keeps each
  canvas. Keep pose scale and padding consistent and inspect the returned preview.

Each new output directory contains `master.aseprite`, cleaned frame PNGs,
`sheet.png`, `sheet.json` and `qa.json`. The native master is reopened through
[Aseprite's Lua API](https://www.aseprite.org/api/sprite); every rendered frame,
the tag range, durations and sheet pixels are checked before an atomic,
no-overwrite directory publication. Source PNGs are preserved. Inputs are bounded
to 4 million pixels each, 16 million total, 128 frames and 1 million output pixels.

The [generated courier demo](examples/generated-demo/README.md) includes the
original model output, a cleaned base, a 4-frame idle, editable masters and a
reproducible local script. Pixel verification proves file integrity. It does not
approve art quality or make a coherent animation out of inconsistent key poses.

The [walk demo](examples/walk-demo/README.md) uses an 8-pose generation pass,
128px-high cells, a shared 48-colour palette and explicit feet anchors. It includes
a looping GIF and a 64px/128px comparison from the same source and palette.

1. `health_check` proves the local CLI and Lua path can create and export.
2. `create_sprite` creates an editable `.aseprite` master.
3. `ase_apply_and_preview` applies a typed batch in one transaction and returns a
   rendered PNG. A failed operation leaves the file unchanged. A later preview
   failure explicitly reports that the edit was saved.
4. `ase_preview_animation` shows every frame without smoothing.
5. `ase_validate_asset_contract` checks geometry, frame count, exact tag ranges,
   tag playback, palette membership, binary alpha, empty frames and optional
   per-row foot/centre drift. Unknown fields are errors.
6. `ase_export_phaser` creates a bundle directory with sheet PNG, hash atlas JSON,
   animation timing, loader JS, contract and QA. It reopens the master and exported
   PNG and compares every frame's RGBA pixels before publishing. An existing
   directory is rejected unless `replace=true`, which swaps a previous bundle by
   rename. The native master remains.

### Building a cast of characters

1. `draw_pixel_rows` paints a whole frame from text rows and a colour key, e.g.
   `rows=["..oo..", ".oxxo."]`, `key={"o": "#203040", "x": "#f0c060"}`. Unknown
   characters are errors; `.` and space are skipped.
2. Animate with the `copy_cel` and `offset_cel` batch ops (bobs and steps without
   redrawing) and tag each animation.
3. `derive_mirrored_tag` creates `walk_left` from `walk_right`.
4. `save_sprite_as` a template, then `recolor_sprite` it into each character, or start
   characters from a shared base with `copy_frames_from`.
5. `ase_preview_animation` shows one labelled row per tag over a checkerboard.
6. `ase_export_cast_atlas` validates every master against its own contract and
   writes one Phaser atlas: frames `<character>/<tag>_<nn>`, animations
   `<character>:<tag>`.

Edits save through a sibling temporary file and a rename, so a killed Aseprite
process cannot leave a half-written master. Frame numbers must exist: an
out-of-range frame is an error, never clamped to the nearest frame.
All frames are 1-based. A contract row names an exact contiguous animation tag.
Palette entries are inline hex colours, not arbitrary file paths. Hide reference
layers before final validation/export: locked layers are still visible pixels.

```json
{
  "name": "robot",
  "cell": {"width": 16, "height": 16},
  "rows": [
    {"tag": "walk_down", "frames": 4},
    {"tag": "idle", "frames": 2, "playback": "pingpong"},
    {"tag": "sleep", "frames": 2, "repeat": 0, "check_anchor": false}
  ],
  "alpha": "binary",
  "palette": ["#315c88", "#8bd5ed", "#223344"],
  "anchor": {"max_foot_drift": 0.0, "max_center_drift": 0.0}
}
```

## Verification

```powershell
uv run pytest
uv run pytest --run-aseprite
uv run python scripts/proof_stdio.py --output ./workspace/proof-final
```

Use a fresh output directory for each proof run. The proof talks to the server
through a real MCP stdio client, creates 16 distinct synthetic frames, compares a
saved/reopened master, exports a sheet and GIF, tests rollback and overwrite/path
rejection, and confirms that a deliberately damaged asset cannot pass export QA.
It writes `proof.json` and visual artifacts. See [verification](docs/VERIFICATION.md).

To play that output in Phaser 3.90.0:

```powershell
npm ci --prefix examples/phaser --ignore-scripts
uv run python -m http.server 8795 --bind 127.0.0.1
```

Open `http://127.0.0.1:8795/examples/phaser/`. The page reports success only after
all four frames in all four animations have played. It uses a local npm dev
dependency; the MCP itself has no network dependency after installation.
For a fresh run under `workspace/my-proof`, use
`http://127.0.0.1:8795/examples/phaser/?proof=my-proof` to play that run's output.

## Boundaries and current limits

- Stdio only; no live-editor bridge or network listener in the MCP server.
- Paths are confined to the workspace and explicitly configured allowed roots.
  `ASEPRITE_MCP_ALLOW_ABSOLUTE=1` allows absolute syntax, never unrestricted access.
  Additional roots: `ASEPRITE_AGENT_ALLOWED_ROOTS='["/absolute/asset/root"]'`.
- Traversal, symlink escapes, unsafe Windows device/stream paths and filename
  template expansion are rejected. Some inherited tools are deliberately hidden.
- This is a local trusted-user tool, not an OS sandbox. Do not let untrusted local
  processes swap symlinks or edit the same master concurrently. MCP calls sharing
  a workspace use an OS advisory lock; a concurrent call waits up to
  `ASEPRITE_MCP_LOCK_WAIT` seconds, then fails with "Workspace busy". All servers writing
  shared assets must use the same workspace, including when using extra roots.
  Direct Python calls, the editor GUI and other programs do not take this lock.
- QA compares composite RGBA pixels. It does not prove artistic quality, correct
  anatomy/motion, palette-index identity, anchor semantics or absence of smoothing
  within an allowed palette. Anchor checks suit grounded same-subject animation.
- The test fixture is procedural evidence, not production walk-cycle art.
- Phaser bundles are validated in staging and published with one atomic,
  no-overwrite directory rename on supported local Windows, Linux and macOS
  filesystems. Unsupported rename semantics fail closed. Process termination
  leaves the final path absent or complete; it can leave a hidden staging folder.
  This does not guarantee durability through power loss or network filesystem behaviour.
- Live-editor turn Undo, private project contracts, broader resampling/fringe
  checks and formal visual golden baselines remain work.

## Releasing

The tag workflow builds, checks and publishes to PyPI via trusted publishing,
then validates and publishes the MCP Registry entry. Configure PyPI's trusted
publisher for this repository, `release.yml` and the `pypi` environment first.
The package version comes from `src/aseprite_mcp/__init__.py`.

When GitHub Actions is unavailable, `uv run python scripts/release --tag v0.2.0`
checks the clean tree, lint, tests and distributions, then prints the publish
commands without sending anything. `--publish` additionally requires an explicit
`UV_PUBLISH_TOKEN` in the environment and a local `mcp-publisher` executable.
It runs `uv publish`, registry validation, interactive GitHub login and registry
publication. Run it only after reviewing the release and setting up the token.

[Security](SECURITY.md) · [Roadmap](ROADMAP.md) · [Changes](CHANGELOG.md)
