Skip to main content
Glama

lpc-spritesheet-mcp

Give an AI agent a character artist's hands. This is the Universal-LPC-Spritesheet-Character-Generator web app re-implemented as a headless Node.js MCP server: an agent describes a character, and gets back a game-ready 832×3456 spritesheet with the exact frame layout — no browser, no screenshotting, no manual export button.

  • 657 selectable items across 104 groups on the current asset pack: 6 body types (male, female, teen, child, muscular, pregnant), 91 hairstyles, 45 heads (human, beast, reptile, undead, …), clothes, armor, 40 weapons, 9 shields with heraldic patterns, hats, capes, tails, wings, prostheses, wheelchairs.

  • Named palette recoloring for skin, hair, eyes, cloth, metal and wood — 22 skintones, 26 hair colors, a 75-color universal palette.

  • 15 animation row blocks on the standard sheet: 13 run in all four directions, hurt and climb ship a single facing — that is exactly the 54 rows. Plus custom-animation blocks for wheelchairs and 128/192px oversize weapons and tools.

  • Engine-ready output. Every render returns rows, frame counts, playback cycles and custom-block offsets, and export_character fans the same character out into per-animation or per-frame PNGs with a TexturePacker/Phaser JSON Hash atlas and a manifest, so a game engine never has to guess the layout or slice the sheet itself.

  • Deterministic, and now proven. npm run golden records SHA-256 hashes of seven canonical renders against a pinned asset-pack revision, and the test suite fails if a render drifts.

  • Attribution included. Each sheet gets -credits.csv / -credits.txt beside it naming the authors and licenses of the art actually used.

The server ships no art. It reads a local clone of the upstream asset pack and renders it, entirely offline: no network calls, no telemetry, and one environment variable (LPC_ASSETS_DIR).

Install

The server needs the upstream asset pack once. Any of these three lines does it — pick one:

git clone --depth 1 https://github.com/LiberatedPixelCup/Universal-LPC-Spritesheet-Character-Generator ~/.lpc-spritesheet/lpc-generator-src
lpc-spritesheet-setup           # same thing, via the npm bin after `npm install -g lpc-spritesheet-mcp`
npm run setup-assets            # same thing, from a checkout of this repo

Then wire the server into an MCP client: npx -y lpc-spritesheet-mcp, or npm install -g it and run lpc-spritesheet-mcp. Prefer a checkout? git clone this repo and run node src/index.js. Updating assets is a git pull in the pack clone.

The pack is looked up in this order, and the first valid entry wins:

  1. $LPC_ASSETS_DIR

  2. ../lpc-generator-src (sibling of the package — the dev-checkout convention)

  3. ~/.lpc-spritesheet/lpc-generator-src (where the setup command above clones)

A valid pack contains both sheet_definitions/ and spritesheets/. If none is found the server prints what it searched for and exits — it will not start half-configured.

Related MCP server: mnehmos.aseprite.mcp

Try it now

No MCP client required — the official protocol inspector drives the server from a shell:

# list the nine tools and their schemas
npx -y @modelcontextprotocol/inspector --cli node src/index.js --method tools/list

# or render a walk cycle and print the JSON payload that names the PNG it wrote
npx -y @modelcontextprotocol/inspector --cli node src/index.js --method tools/call \
  --tool-name get_preview --tool-arg animation=walk --tool-arg scale=3 --tool-arg bodyType=female

# or drive it interactively: browse 657 items, call tools, view the result
npx @modelcontextprotocol/inspector node src/index.js

MCP client configuration

All of these run the same stdio server. LPC_ASSETS_DIR is optional — omit it if the pack is in one of the two automatic locations above.

{
  "mcpServers": {
    "lpc-spritesheet": {
      "command": "npx",
      "args": ["-y", "lpc-spritesheet-mcp"]
    }
  }
}
claude mcp add lpc-spritesheet -- npx -y lpc-spritesheet-mcp
{
  "mcpServers": {
    "lpc-spritesheet": {
      "command": "npx",
      "args": ["-y", "lpc-spritesheet-mcp"],
      "env": { "LPC_ASSETS_DIR": "D:/assets/lpc-generator-src" }
    }
  }
}
{
  "mcp": {
    "servers": {
      "lpc-spritesheet": { "type": "stdio", "command": "npx", "args": ["-y", "lpc-spritesheet-mcp"] }
    }
  }
}

The nine tools

Tool

Purpose

list_categories

All selection groups (body, hair, head, clothes, legs, shoes, weapon, hat, shield, tail, wings, …) with item counts and whether they take colors. Start here.

list_items

One category: exact itemIds, display names, pre-sliced variants, valid recolors — deduplicated into a response-level palettes map. Filter with search, page with limit/offset, shrink with fields; the response is byte-budgeted, so check truncated/nextOffset.

list_palettes

Color names per material (body, hair, eye, cloth, metal, wood, all), with each material's default.

list_animations

The row layout of every animation (rows, frame counts, playback cycles, y-offsets) plus the custom blocks, which items provide them, and whether any item does.

generate_spritesheet

Render the full universal sheet; returns the path, layout, warnings, credits and the asset-pack revision.

get_animation

Render, then crop ONE animation into its own PNG — standard row block or custom block.

get_preview

Render, then crop one direction's playback cycle, upscaled 1–8×. Small enough to return inline, so you also get the image itself, not just a path.

export_character

Render, then fan out into a directory bundle: sheet.png, a TexturePacker/Phaser JSON Hash atlas of every frame rect, manifest.json, and either one PNG per animation row block (mode: "animation") or one per frame (mode: "frames"), plus credits.

validate_sheet

Audit instead of render: the port's row-block tables against the pack (layout), a character's painted columns against the declared frame counts (character), or item definitions against what this port can resolve (catalog). Writes nothing; ok: false names what failed.

The render tools share one argument block: bodyType, selections, colors, character, match_body_color, include_credits, include_character_json — plus output_path for the three single-file tools and output_dir for export_character. Item IDs, display names, "none", and {itemId, variant?, recolor?} objects are all accepted per group. character takes the web app's saved-character JSON ({version: 2, selections}, or the fuller document this server writes) or a v1 document with a #sex=…&hair=… state hash; explicit selections/colors merge on top. A v1 hash that names a retired group or a stale color comes back as warnings, not a failure. Defaults mirror the web app — light-skinned body, human male head, neutral expression (note the head default is not body-aware).

One call, start to finish

Ask your agent for "a female archer with red hair, a teal hood and a recurve bow, walking toward the viewer", or call the tool directly:

// tools/call → get_preview
{
  "bodyType": "female",
  "selections": { "hair": "Bangs", "clothes": "Longsleeve", "weapon": "Recurve", "hat": "Hood" },
  "colors": { "body": "amber", "hair": "red", "clothes": "teal", "eyes": "green" },
  "animation": "walk",
  "direction": "down",
  "scale": 3,
  "output_path": "sprites/archer-walk.png"
}
// ← result
{
  "output": "…/sprites/archer-walk.png",
  "bodyType": "female",
  "image": { "width": 1536, "height": 192, "frameSize": 192 },
  "animation": "walk", "direction": "down", "scale": 3,
  "frames": [1, 2, 3, 4, 5, 6, 7, 8],
  "warnings": [
    "weapon_ranged_bow_recurve: no sprite for row(s) walk, custom:walk_128 — those rows will be empty (2 missing)"
  ],
  "selections": {
    "body": "body (amber)", "head": "heads_human_male (light)", "expression": "face_neutral (light)",
    "hair": "hair_bangs (red)", "clothes": "torso_clothes_longsleeve (teal)",
    "weapon": "weapon_ranged_bow_recurve [light]", "hat": "hat_hood_cloth (brown)",
    "eyes": "heads_human_male [green] (color sub-selection)"
  },
  "credits": { "csv": "…/archer-walk-credits.csv", "txt": "…/archer-walk-credits.txt", "entries": 7 }
}

That warning is the feature working: the recurve bow's definition claims a walk cycle but the pack ships no walk.png for it, so the engine renders the archer, leaves the bow's walk row empty, and tells you — one aggregated message per item, never a failure. A test renders this exact call and asserts these exact numbers and this exact warning, so the example above cannot rot. Note also that hat_hood_cloth stayed brown: colors.clothes recolors the torso group, not every cloth item.

Relative output_paths resolve against the server's working directory, not the client's. Omit output_path entirely and the sheet lands in lpc-spritesheet/ under the OS temp directory, with a timestamp suffix so two renders never overwrite each other.

What comes out

Rendering is fast enough to iterate on inside a conversation, but the number that matters is the first call, not the steady state. Measured on Node 24 / Windows against pack revision 553ba75: ~200 ms to load the 657-item catalog, ~1.1 s from process start to the server's ready line, ~0.53 s for the first 832×3456 sheet while sprite PNGs come off disk, then 3–10 ms per further render (including a weapon sheet with 128px custom blocks) once the engine's image cache is warm — an animation crop ~8 ms, a preview strip ~80–140 ms. A fresh MCP client pays the cold path once per process.

How it works

src/index.js is the MCP surface, src/character.js is the request pipeline, and src/engine/ is a headless port of the upstream renderer: catalog reads the pack's JSON definitions, selection normalizes what the agent asked for, recolor does CPU palette-swapping, sheet composites the draw calls into the frame grid, and credits collects attribution. The engine knows nothing about MCP.

docs/ARCHITECTURE.md has the upstream-to-here map, every intentional divergence, and diagrams of the module graph, one render call, and where files land on disk.

Sheet layout (stable)

13 columns × 54 rows of 64px frames = 832×3456. Directions run up, left, down, right within each row block:

Animation

Row

Frames/dir

Animation

Row

Frames/dir

spellcast

0

7

sit

30

3

thrust

4

8

emote

34

3

walk

8

9

run

38

8

slash

12

6

combat_idle

42

2

shoot

16

13

backslash

46

13

hurt

20

6 (1 row, down)

halfslash

50

6

climb

21

6 (1 row, up)

idle

22

2

jump

26

5

watering and 1h_slash are aliases that resolve onto another block's frames (thrust and backslash); combat and 1h_backslash/1h_halfslash own their rows and only differ in the folder the pack ships them in. Custom blocks (64–192px frames) are appended below row 54 and can widen the canvas to 1664px; the result payload reports every offset, so a consumer never has to derive them.

Known upstream asset gaps surface as warnings, one message per item — e.g. the child body ships only hurt/idle/jump/sit/slash/walk, so its other rows come back empty by design, not by bug.

Licensing — read this before you ship

  • This server's code: GPL-3.0-only, because it derives from the GPL-3.0 upstream generator. See LICENSE.

  • The sprites it renders come from the Liberated Pixel Cup pack: a mix of OGA-BY 3.0, CC-BY-SA 3.0, CC-BY 3.0/4.0 and GPL held by many artists. Every render writes *-credits.csv / *-credits.txt beside the PNG listing exactly the authors, licenses and links for the assets used. Keep those files when you redistribute renders.

  • The images in this README are themselves renders, so they carry attribution too: docs/img/showcase-credits.csv and docs/img/demo-credits.csv.

Development

npm install
npm run setup-assets    # or lpc-spritesheet-setup if you installed globally
npm test                # 66 passing items: 25 engine, 16 stdio MCP, 15 surface, 9 golden, 1 smoke
npm run golden          # re-record test/golden.json after an intended rendering change
npm run assets          # regenerate every committed image (docs/img/)

npm test is the only automated gate — there is no lint, format or typecheck tooling in this repo, so nothing else is claimed. CI runs the suite on Node 20 and 22 across Ubuntu and Windows, and asserts that the server fails cleanly when the pack is missing.

See CONTRIBUTING.md, the issue templates under .github/, docs/ARCHITECTURE.md and docs/capability-inventory.md (everything the upstream generator can do).

Acknowledgements

Built on the Universal-LPC-Spritesheet-Character-Generator by Johannes Sjölund (wulax), bluecarrot16, Stephen Challener (Redshrike), ElizaWy, JaidynReiman and the many LPC artists credited in its CREDITS.csv — this project exists to make their work usable from AI agents. All sprites remain the property of their respective authors under the licenses noted above.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Generates random Universal LPC spritesheet characters as full animation PNGs, compositing real sprite layers via a Cloudflare Worker with browser rendering.
    1
    -
  • 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
  • F
    license
    B
    quality
    C
    maintenance
    Enables users to automate the creation, editing, animation, and export of pixel-art game assets through a multi-agent pipeline that derives style specs and blueprints, generates Lua build scripts, and drives the canvas editor to produce .aseprite files, spritesheets, atlases, GIFs, and previews.
    12
    -