---
name: spriteoven
description: Generate, organize and export pixel-art game assets with Spriteoven — characters, item sets, FX, decor, UI kits and biome tilesets — through the Spriteoven MCP server. Use whenever the user asks for game sprites, pixel art, a tileset, an item set, an icon pack, UI frames, or wants to export existing assets to Godot or Unity.
---

# Spriteoven

Spriteoven is a pixel-art asset **library and forge**. The MCP server lets you work in the
user's own account: read what they already have, generate new pieces, and export them in
engine format.

**Generating spends the user's credits.** Read the consent rule below before anything else.

---

## 🔴 The consent rule — read this first

Every `generate_*` tool works in **two steps**, and you must not skip the first one.

1. **Call it WITHOUT `confirm_token`.** Nothing is spent. You get the exact price, the
   balance before and after, and a `confirm_token`.
2. **Show that price to the user and ask.** Wait for an actual answer.
3. **Call it again with the SAME parameters plus `confirm_token`.** Now it generates.

The quote is signed over the parameters and lasts **300 seconds**. If you change a single
parameter, the token stops working — that is deliberate, so a quote can never be used to buy
something more expensive than what the user approved.

**Never batch approvals.** "Generate 5 effects" is five prices. Quote them, add them up, show
one total, and ask once — but confirm each one only after the user says yes.

If the user has already said "go ahead, don't ask me again for this batch", you may confirm
without asking again *within that batch*. A new request is a new approval.

---

## The two things to do before you generate anything

**1. Look at the balance.** `get_credits`.

> ⚠️ The free tier counts **CALLS, not credits**. One `high` generation burns the same
> free generation as a `low` one — it is not prorated. `free_gens_remaining` and
> `credits_balance` are two different budgets and both are in the response.

**2. Look at what they already have.** `list_assets` (optionally with `family`, `tag`, or
`query`). Re-generating something the user already owns costs them money for nothing. This is
the single most valuable thing you can do here, and it is free.

`list_assets` hides variants by default (a character can have dozens of child views); pass
`include_variants: true` only when you actually need them. It returns real `total` and
`has_more` — if `has_more` is `true`, there is more, and if it is `null` the server could not
count, which is not the same as "no more".

---

## The families

| family | generate by MCP? | export by MCP? | what it is |
|---|---|---|---|
| `items` | ✅ `generate_item_set` | ✅ | sets of items / equipment pieces |
| `fx` | ✅ `generate_fx` | ✅ | effects and spells, animated |
| `deco` | ✅ `generate_deco` | ✅ | scenery props, static or animated |
| `uikit` | ✅ `generate_uikit` | ✅ | buttons, windows, bars, 9-slice frames |
| `personajes` | ❌ | ✅ Godot · PNG | character sprites |
| `mapas` | ❌ | ✅ painted map: Godot · Unity · Tiled — biome: its tileset ZIP | painted maps and biome tilesets |

**Characters and maps cannot be generated through MCP**, and the reason is not that nobody got
around to it: those two pipelines charge per generation inside the worker and do not publish a
price function, so there is no exact price to show the user before spending. Since the whole
consent flow rests on the price being exact, they are not served here. Tell the user to make
those in the web app (Sprite Lab) — they can still `list_assets`, `get_asset`, read them
and **export** them here afterwards.

---

## Style: what to pick, and for which size

Call `list_styles` — it returns each family's real catalogue with a one-line `promise` for each
rung. There are **three separate style axes**, and mixing them up is the most common mistake:

- **The art ladder** (`personajes`, `items`, `deco`, `uikit`): `hd` · `pixel-modern` ·
  `pixel-classic` · `8-bit` · `pixel-chunky` · `cel-shaded`. Default is `pixel-modern`.
- **FX** uses the same ladder plus `hyperreal`.
- **Maps** uses a different axis entirely — the biome **surface**. Do not ask a biome for a
  character style.

**Which rung for which pixel size.** `list_styles` marks this with `recommended_px` when the
product declares it. What it says today:

- **32 px and 48 px → `8-bit` or `pixel-chunky`.** These are the *structural* rungs: fat
  blocks, short palette. At small sizes they are the ones that stay readable.
- **Larger canvases → `pixel-modern`, `pixel-classic`, `cel-shaded` or `hd`.** These are
  *palette* rungs: fine pixels and lots of colour, which need room to read.
- `hyperreal` (fx only) is a *photo* rung — no outline, no pixels. It is a deliberate break
  from the rest of a pixel set; do not reach for it unless the user asks for that look.

Rule of thumb: **if the sprite is 48 px or smaller, go structural.** A `pixel-modern` icon at
32 px turns to mush.

When the user names a style in plain words, match it to a `style_id` from `list_styles` rather
than inventing one — the forge rejects ids it does not know.

---

## Generation is asynchronous

A confirmed `generate_*` returns a **`job_id`**, not an asset. That is a queue ticket.

Poll `get_job(job_id)` every ~5 seconds until `done` is `true` or `failed` is `true`. When it
is done it hands you `asset_ids`.

**If a job fails, do not silently retry.** Every attempt costs the user. Tell them what failed
and ask.

Typical wall-clock: tens of seconds to a few minutes, depending on family and quality.

---

## Exporting

`export_asset(asset_id, engine, size|scale)` returns a **signed URL to a .zip**, never bytes.
The URL lasts one hour and the .zip is **built again when it is downloaded** — nothing is stored.
If it expired, just call `export_asset` again: it costs nothing.

Get the valid `engine` and size values from `get_asset(asset_id)` → `formats`. Do not guess
them: they differ per family, and `formats.reason` explains the gap — either a missing export
ladder, or a missing **engine axis** on a family that does have a ladder (an item set re-cuts by
`item_size` and a gear piece by a probed `size`; neither has an engine to choose). A `null` in
`formats` always arrives with its reason.

**Characters and maps are built from the library, so they never expire.** A character exports
with **every animation it has saved** — one per action and direction (`walk_s`, `walk_e`,
`attack_ne`…), each from its latest retouched version — as a Godot `SpriteFrames` or as PNG
sheets. Call it on the character or on any of its views: you get the whole character. A painted
map exports to Godot, Unity or Tiled; a biome comes as the tileset ZIP its forge saved.

> ⚠️ **fx, deco, uikit and item sets are rebuilt from their generation job.** Some OLDER assets of
> those four families lost that package to an earlier retention sweep and answer
> `EXPORT_PACKAGE_EXPIRED` — expected, not a bug. Their raw master PNG is still available through
> `get_asset` (`master.url`), which is often all the user needs.

Spriteoven is the **source**. If the user also has a pixel-editor MCP connected (Aseprite,
Godot), the natural division of labour is: generate and version here, refine and sync there.

---

## Painting a map

A map in **Maps → Paint** is painted with the user's own biomes and Deco pieces. Painting costs
**0 credits** — the tilesets and pieces are already paid for.

- `get_map({ world })` — the world's palette: its terrains and the pieces you can place, with
  their size in cells. Without `world`, it lists the user's maps.
- `get_map({ map_id })` — the map as **text**: one letter per cell, with column and row rulers.
  The legend says which terrain each letter is and which one sits on top. Objects are listed
  with the cell of their **foot** (bottom row, left cell).
- `edit_map` — the Paint brush as operations: `paint`, `rect`, `path`, `fill`, `place`,
  `remove`, `move`, `resize`. Terrains go by letter or by name; `"none"` erases.

**The consent rule applies even at 0 credits**: without `confirm_token`, `edit_map` saves
nothing and returns a **preview** — the changed area as text, the legend, where each object
lands, and an error with a code for any operation that cannot apply. Show it to the user.
Only then call again with the same parameters, `base_version` from the preview, and the token.
Saving adds a new version; earlier ones stay in the Library.

If the map changed since you read it you get `MAP_CHANGED` — read it again rather than
forcing. Which terrain covers which, and the edge between terrains, belong to the **world**:
the user sets them in the world strip; they are not `edit_map` operations.

The user exports a painted map from **Maps → Paint → Export** (Godot, Unity or Tiled) — or you
do, with `export_asset` on the map's `asset_id`.

---

## A good session, end to end

> *"Make me a medieval item set in 8-bit and give it to me for Godot."*

1. `get_credits` — check there is budget.
2. `list_assets({ family: "items" })` — maybe they already have one.
3. `list_styles({ family: "items" })` — confirm `8-bit` is a real rung.
4. `generate_item_set({ description: "medieval items", count: 6, style: "8-bit" })` — **no
   token**. You get a price.
5. **Show the price. Ask.**
6. Same call **plus `confirm_token`**. You get a `job_id`.
7. `get_job` every ~5 s until done.
8. `get_asset` on the result to read `formats`, then `export_asset` with the engine it lists.
9. Give the user the download URL and say when it expires.

---

## Errors worth recognising

Errors come back as `[CODE] message`. The code tells you what to do:

| code | what it means | what to do |
|---|---|---|
| `MCP_SCOPE_MISSING` | the token cannot spend | ask the user for a token with the `generate` scope |
| `MCP_TOKEN_REVOKED` / `MCP_TOKEN_EXPIRED` | the connection is dead | ask them to create a new token; retrying will not help |
| `QUOTE_EXPIRED` | the 300 s quote lapsed | quote again (call without `confirm_token`) |
| `QUOTE_PARAMS_CHANGED` | parameters moved after quoting | quote again — never try to force it |
| `INSUFFICIENT_CREDITS` | not enough balance | tell them the shortfall; the message has the numbers |
| `UNKNOWN_FAMILY` | bad family name | the message lists the valid ones |
| `ASSET_NOT_FOUND` | not in their library | do not retry; `list_assets` to find the real id |
| `EXPORT_PACKAGE_EXPIRED` | an old fx/deco/uikit/item job lost its package | offer `get_asset` → `master.url` instead |
| `NO_ANIMATIONS` | the character has no saved action yet | ask the user to make one in Sprite Lab |
| `EXPORT_ENGINE_NOT_OFFERED` | that engine is not offered for this asset | the message lists the ones that are |

---

## Things not to do

- **Do not spend without showing the price.** Nothing on the server can force you to ask a
  human — the whole design assumes you will.
- **Do not re-generate what `list_assets` already shows.** Looking is free; generating is not.
- **Do not retry a failed generation on your own.**
- **Do not inline images.** Every image is a URL. Pass the URL along; do not fetch it into
  context.
- **Do not invent style ids, engines, sizes or UI component names.** Read them from
  `list_styles` / `get_asset`, and read the component names from the `component` enum that
  `generate_uikit` publishes in its own schema. Its three old examples (`marco`, `boton`,
  `ventana`) were in Spanish and the endpoint rejected all three — **after** quoting a price.
  Fixed 2026-09-10 (PULIDO-1a §5): the vocabulary is now in the schema, so a client that
  enforces schemas catches a wrong name before the call goes out.
