# Spriteworks MCP

Agents can search the same approved artwork used by the Design site, compose bunnies, recolor logos and poses, and export files without a browser. The server also accepts agent-authored SVG geometry. Rendering uses the server's local artwork and needs no cloud generation service.

## Start here

**MCP runs locally. GitHub Pages hosts the website and agent instructions.** No shared HTTPS endpoint is configured. Visiting this website does not connect an MCP server or grant an agent filesystem access. Configure the local server in your MCP client using the steps below.

The site and repository require the organization's GitHub access. An unauthenticated cloud crawler may see a sign-in page. Use an authenticated browser or an authorized local checkout. Site sign-in and an MCP endpoint's bearer token are separate credentials.

The site publishes `llms.txt`, `mcp-guide.md`, `mcp-manifest.json`, `mcp-tools.json`, `mcp-capabilities.json`, `mcp-examples.json`, and `mcp-client-config.json`. These are static documentation files. `mcp-tools.json` is generated from the real server's tool schemas at build time. After connecting, use the live `tools/list` and `get_capabilities` responses as the authority for that server version. The same guide and examples are available as `spriteworks://guide` and `spriteworks://examples` MCP resources.

## Local connection

Use Node 22.22.1. Clone the repository with an authorized GitHub account, then install dependencies:

```sh
git clone https://github.com/autonomouscyberco/spriteworks.git
cd spriteworks
npm ci
```

If a checkout already exists, use it instead. The catalogs in `dist/` are tracked; after editing artwork, run the repository's normal asset build. Python is not needed just to launch MCP and export from the existing catalog.

Configure your MCP client using [client-config.example.json](./mcp-client-config.json), replacing the two absolute paths. Launch `node /absolute/path/to/spriteworks/tools/spriteworks-mcp.mjs` directly. Using `npm run mcp` inside an MCP client's command may add npm text to the protocol stream.

After updating the checkout, restart or reconnect the MCP server in your client so it discovers newly added tools.

## Refreshing artwork

Use the [artwork intake guide](./intake-guide.md) to compare a fresh Bunnies export with the approved source. `npm run intake:report` produces a reviewable change report with explicit placeholder exclusions and affected fit contexts. Intake runs before the offline build and never changes the catalog automatically. Agents can also read the guide at `spriteworks://intake`.

The default output directory is `output/mcp/` in this repository. Override it with `--output-dir` or `SPRITEWORKS_OUTPUT_DIR`. Each export gets its own UUID directory. Source artwork and existing exports are never overwritten. Files persist until the operator removes them; there is no automatic retention policy.

## Agent workflow

1. Call `get_capabilities` for formats, controls, palette references and limits.
2. Call `list_assets` with a kind and search query, then `inspect_asset` for exact IDs, color roles and compatibility.
3. Call `export_asset` for an individual asset or `export_sprite_sheet` for a grid. When a combined freeform layout is requested, the optional `compose_image` tool can place multiple assets on one canvas.
4. Inspect the returned preview, dimensions, frame counts and print warnings. Check `isError` before claiming success.
5. Use the returned local paths or MCP resource links. `read_export` reads large files in base64 chunks when a client cannot use resources.

Every export includes a reproducible JSON recipe and, by default, a PNG preview. Tool errors explain invalid IDs, incompatible pieces, unsupported formats or exceeded budgets. One rendering job runs at a time. Cancelling a tool request terminates its worker; a concurrent export receives a retryable busy error.

## Tools

| Tool | Controls and result |
| --- | --- |
| `get_capabilities` | No arguments. Returns formats, palette roles, reference swatches, features and limits. |
| `list_assets` | Optional `kind`: `logo`, `pose`, `animation`, `rig`, `part`, `anatomy`, `base`. `query`: up to 160 characters. `offset`: zero-based, default 0. `limit`: 1-100, default 30. Follow `nextOffset` until null. |
| `inspect_asset` | Required `kind` and exact `id`. Returns color roles, frame information and applicable body compatibility. Check `available` for static construction and `liveRigFit` for dressed motion. |
| `export_asset` | Required `asset`; all sizing, style, animation and print controls below. Returns a new export ID, dimensions, files, resource links and optional PNG preview. |
| `export_sprite_sheet` | Required `assets`; cell sizing, columns, gutters and margin controls below. Returns a new sheet export and atlas metadata in its recipe. |
| `compose_image` | Optional. Required final `canvas` dimensions and `layers`. Combines independently positioned, sized, rotated and colored artwork into one still image. Use when a combined layout is requested. |
| `read_export` | Required `exportId` and `filename` from the export result. Byte `offset` defaults to 0. `length` is 1-1,048,576, default 65,536. Returns `base64`, `totalBytes`, and `nextOffset`. |

### Logo example

```json
{
  "asset": { "kind": "logo", "id": "fuzze-logo" },
  "format": "png",
  "name": "fuzze-cyan-banner",
  "canvas": {
    "width": 1200,
    "height": 400,
    "padding": { "top": 40, "right": 80, "bottom": 40, "left": 80 },
    "alignX": "left",
    "fit": "contain"
  },
  "style": { "color": "#03E7F3", "background": "transparent" }
}
```

### Composition and animation

`asset.kind` supports `logo`, `pose`, `animation`, `rig`, `bunny`, and `svg`. A bunny accepts `base`, `body`, `head`, `ears`, `leg`, and `parts`. Discover their IDs first. Omit anatomy choices for the authored defaults; `head`, `ears`, and `leg` can be `none` where supported.

Use `rig` for a dressed pose, animation or sprite sheet. Its `id` is a discovered fitted motion ID, `parts` accepts up to nine reviewed item IDs with one per slot, and optional `frame` selects a still pose. Inspect each piece's `liveRigFit` first. Headwear and outfits that own their ears suppress a separate ear selection; the result records selected, active and suppressed items. Every library sequence has an `authored.<motion ID>` fitted track, for example `authored.eatCarrot` or `authored.imposterFly`. The hand or mouth stays occupied during authored food/computer use. Equipment follows hole clipping, reveals, dissolves and outlines; blank transition frames stay blank. Native reference tracks retain their original behavior. Monster and Zzz have dedicated moving fits even though their static construction cards use different bases. Pink placeholder cards have no fit.

Rig export shares the website's fitted layer order, limb support and palette rules. Every export records its fit revision and source frames. A stale fit pack fails explicitly. This exports reviewed poses; agents cannot invent new joint geometry or author new fits through this tool.

An animation accepts its clip ID and an optional zero-based `frame` for a still export. Omit `frame` for an animated export. `fps` supports 1 through 60 and `playback` supports `loop` or `ping-pong`. All frames share a fitting envelope to prevent recentering during playback. WebP may combine duplicate frames while preserving their duration; `frameCount` reports encoded frames and `sourceFrameCount` reports input frames.

`canvas` supports arbitrary rectangular dimensions, pixel padding per side, left/center/right and top/center/bottom alignment, contain/cover/stretch fitting, and content/canvas bounds. Omitted dimensions preserve the artwork aspect ratio with a default longest edge of 512 pixels. Bunny content fitting preserves the shared body anchor and authored headroom. Logo and custom-SVG bounds follow their authored canvas.

`style.colors` overrides named semantic roles, while `style.color` changes the primary body/logo color. Colors are six-digit RGB hex values. Unknown roles fail explicitly. Backgrounds are transparent or solid RGB. Mirroring preserves padding/alignment. `pixelated` defaults on for bunny art and off for logos/custom SVG.

## Every export control

| Field | Values / default |
| --- | --- |
| `asset` | `logo` or `pose` with `id`; `animation` with `id` and optional zero-based `frame`; `rig` with motion `id`, fitted `parts` and optional `frame`; `bunny` with optional `base`, `body`, `head`, `ears`, `leg`, `parts`; or `svg` with a complete `svg` string. |
| `asset.parts` | Up to 32 part IDs for a static bunny, or nine fitted IDs for a rig with one per slot. Static `base` uses a base ID; `body`, `ears` and `leg` use compatible anatomy IDs. Omit these for defaults. `head` is `original` or `none`; `ears` and `leg` also accept `original` or `none`. |
| `format` | Exact format identifiers are in the table below. Default `png`. |
| `canvas.width`, `canvas.height` | Integer pixels, 1-8192. Omit one to derive it from the artwork aspect ratio. Omit both for a 512-pixel longest edge. |
| `canvas.padding` | Pixels, 0-4096, default 0. One number or an object containing all four of `top`, `right`, `bottom`, `left`. Must leave room for artwork. |
| `canvas.fit` | `contain` (default) keeps all art; `cover` fills and crops; `stretch` changes its proportions. |
| `canvas.alignX`, `canvas.alignY` | `left` / `center` / `right`; `top` / `center` / `bottom`. Both default `center`. |
| `canvas.bounds` | `content` (default) or `canvas`. Bunny content fitting retains its body anchor/headroom; logo/custom SVG use their authored canvas. |
| `style.color` | Primary color as `#RRGGBB`. Omitted: authored palette. |
| `style.colors` | Map of supported semantic roles to `#RRGGBB`. Overrides `style.color` for a matching role. For custom SVG, add `data-role="body"` or another catalog role to geometry to recolor it. Unmarked SVG geometry keeps its fill. |
| `style.background` | `transparent` (default) or `#RRGGBB`. JPEG needs a solid color. |
| `style.mirror` | Boolean, default false. Horizontal reflection. |
| `style.pixelated` | Boolean. Defaults true for bunny art, false for logos/custom SVG. Controls SVG crisp-edge rendering. |
| `fps` | Number from 1-60, default 8. Applies to animation timing. |
| `playback` | `loop` (default) or `ping-pong`. Animated exports repeat indefinitely. |
| `quality` | Integer 1-100, default 95. Applies to JPG, WebP, AVIF and animated WebP. |
| `dpi` | Integer 36-1200, default 96. Raster density metadata and default print sizing. |
| `printSizeMm` | Physical longest edge, 1-1000 mm, for PDF/EPS. Omitted: pixel dimensions divided by DPI, converted to mm. |
| `printMode` | `cmyk` (default) or `spot`. Spot output requires documented color references. |
| `allowEstimatedPrintColors` | Boolean, default true. Set false to reject unverified print-color estimates. |
| `name` | Filename stem, 1-64 letters, numbers, hyphens or underscores; first character alphanumeric. Default `asset`. No path or extension. |
| `includePreview` | Boolean, default true. Adds an inline PNG of the first frame, up to 256 pixels per side. The preview is not the exported file. |

## Optional freeform compositions

`compose_image` creates a single still image from multiple assets, for example a banner with a logo and several bunnies. It is an optional tool: use it when the user wants a combined layout. Individual assets and grids continue to use `export_asset` and `export_sprite_sheet` directly. There is no extra composition step in those workflows and no separate canvas-editor page to open.

| Field | Values / default |
| --- | --- |
| `canvas.width`, `canvas.height` | Required final image dimensions, integer pixels, 1-8192 each, up to 16,777,216 total pixels. |
| `canvas.background` | `transparent` (default) or `#RRGGBB`. JPEG requires a solid final background. |
| `layers` | 1-64 layers, drawn in array order from bottom to top. Later layers cover earlier layers. |
| `layers[].asset` | A logo, pose, built bunny, fitted rig or custom SVG using the same asset fields as `export_asset`. Animation and rig selections must include an explicit zero-based `frame`; composition exports are still images. |
| `layers[].x`, `layers[].y` | Position of the layer canvas's top-left corner before rotation, in final-canvas pixels. Default 0; range -8192 to 8192. Negative positions are allowed. |
| `layers[].canvas` | The existing canvas controls: width, height, padding, contain/cover/stretch, alignment and bounds. These size and fit that layer before rotation. Omit size for a 512-pixel longest edge, or supply one dimension to retain its aspect ratio. |
| `layers[].style` | Independent primary/semantic colors, mirroring, crisp-edge rendering and optional background for each layer. Default background is transparent. |
| `layers[].rotation` | Clockwise degrees around the center of the layer canvas, -360 to 360, default 0. Rotation includes the layer's background and padding. |
| `layers[].opacity` | 0-1, default 1, applied to the entire layer as a group. |
| `format` | `png` (default), `svg`, `jpg`, `webp`, `avif`, `tiff`, `pdf` or `eps`. |
| `name`, `quality`, `dpi`, `printSizeMm`, `printMode`, `allowEstimatedPrintColors`, `includePreview` | Same controls as single exports. Default name `composition`. EPS is process CMYK only; use PDF for Pantone spot inks or partial opacity. |

Artwork outside the final canvas is clipped. Mirroring preserves the layer's padding and alignment; the fitted layer is then rotated and positioned. The sum of all layer-canvas areas is limited to 67,108,864 pixels, and assembled layer SVG geometry is limited to 8 MiB. Custom SVG limits still apply to each custom layer. Inputs reference the approved local catalog or inline SVG geometry; image URLs, local file imports, raster photos, text/fonts and animated compositions are not supported. Text can be supplied as outlined SVG paths.

The result uses the usual export ID, files, resource links and PNG preview, plus `layerCount`. Its `recipe.json` records full inputs, resolved layer dimensions, bunny recipes and catalog revisions. Reorder the `layers` array to change stacking. A complete 1920 by 1080 banner example appears in the runnable examples and `spriteworks://examples`.

## Sprite sheet controls

| Field | Values / default |
| --- | --- |
| `assets` | 1-128 asset selections using the same asset shapes above. Up to 256 frames after expansion. |
| `format` | `zip` (default), `png`, or `svg`. ZIP contains `<name>.png`, `atlas.json` and `recipe.json`. |
| `cell` | Same fields as `canvas`: dimensions, padding, fitting, alignment and bounds. Default 128 by 128 pixels. If a cell dimension is omitted it defaults to 128. |
| `columns` | Integer 1-32, default 8. The requested count is preserved, including empty trailing cells. |
| `gap` | Integer 0-1024 pixels between cells, default 0. |
| `margin` | Integer 0-1024 pixels around the sheet, default 0. |
| `expandAnimations` | Boolean, default true. Expands selected clips into frames. False uses only the first selected frame of each clip. |
| `fps` | 1-60, default 8; recorded as atlas timing. Does not animate the sheet. |
| `style`, `name`, `includePreview` | Same controls as single exports; style applies to every cell. Default name `sprites`. |

Sheet width = `2 * margin + columns * cell.width + (columns - 1) * gap`. Height uses `ceil(frameCount / columns)` rows. The entire sheet must fit the same canvas budget. To use different colors per cell, export individual assets or author a custom SVG; the sheet has one shared style.

## Formats and limits

| Output | Formats |
| --- | --- |
| Still assets | `svg`, `png`, `jpg`, `webp`, `avif`, `tiff`, `pdf`, `eps` |
| Animation | `gif`, `apng`, `animated-webp`, `animated-svg` |
| Sprite sheets | `svg`, `png`, `zip` with PNG + atlas.json + recipe.json |
| Optional compositions | Same still formats; PDF supports named spot inks, EPS uses process CMYK |

- Dimensions: 1–8192 pixels per side, with a 16-megapixel canvas budget.
- Animation: 64 MiB of uncompressed RGBA frames per request. Actual GIF delays use its 10 ms time units.
- Sprite sheets: up to 256 frames, configurable cell dimensions/padding, columns, gap and outside margin. Animation selections expand to frames unless `expandAnimations` is false.
- JPEG requires an explicit solid background. No silent transparency flattening.
- PNG/JPG/WebP/AVIF/TIFF honor `dpi`; lossy formats also honor `quality` (1–100).
- PDF/EPS use the existing vector print pipeline. `printSizeMm` sets the physical longest edge; otherwise dimensions and DPI determine it. CMYK estimates are identified in export metadata. `printMode: "spot"` requires PDF and documented Pantone references. EPS supports process CMYK and rejects partial transparency; unsupported print geometry fails explicitly.
- Custom SVG: maximum 256 KiB, 10,000 elements and 32 nesting levels. Geometry, transforms, fills/strokes and local clipping are supported. Scripts, CSS, images, external references, embedded fonts, filters and foreign objects are rejected. Convert text to paths before exporting.
- Encoded files: maximum 64 MiB. Direct MCP resource reads: maximum 4 MiB; larger files use `read_export` chunks or the local path.
- MP4, WebM, PSD and AI files are not advertised or substituted. Use the capability response rather than assuming every format exists.

## Getting the actual files

The text/structured export result contains `id`, `width`, `height`, `frameCount` and a `files` array. Each file has `name`, server-local `path`, MIME type, and a `spriteworks://exports/...` URI. The main file also has byte length and a SHA-256 checksum. `recipe.json` records the complete inputs and source revisions.

For a local client on the same machine, copy or attach the returned file. For a remote client, the returned path belongs to the server, not your computer: call MCP `resources/read` with the returned URI. If the client lacks resource reads or the file is over 4 MiB, call `read_export` using the returned export ID and exact filename. Start at offset 0, decode each base64 chunk to bytes, append the bytes in order, and use `nextOffset` until it is null. Do not concatenate base64 strings. Verify the completed byte count/checksum, save locally, and attach the result to the user. A `spriteworks://` URI is not a public browser download URL.

Open the preview or output to check it before delivery. For animation, inspect `sourceFrameCount`, encoded `frameCount` and `durationMs`. For sheets, inspect `atlas.json` inside the ZIP or the atlas in the outer recipe. A tool error is a failed request, not a file.

## Scope and common failures

- These tools create new exported files. They do not edit the site's current draft, upload to Figma, publish the site, or mutate the approved source catalog.
- Bunny composition uses the approved parts and compatibility rules. Rig exports use reviewed moving fits. Live joint manipulation, physics and authoring new fits remain controls on the Rigging page.
- An agent may author SVG geometry with this server; it is not a text-to-image model. It does not call an image-generation service.
- Unknown ID or incompatible part: run `list_assets` / `inspect_asset` again, choose available compatible pieces, then retry. `body`, ears and leg choices come from `anatomy`; props/outfits come from `part`.
- Unsupported format: use the exact format names, including `jpg` and `animated-webp`. To export a still from a clip, use a static format and `asset.frame`. An animated format requires a clip with no explicit frame.
- Budget error: reduce dimensions/frame count or split the sheet. Padding must leave a positive interior in both dimensions.
- Busy error: wait for the current export to finish or cancel it through the client, then retry. Exports should run sequentially on each server instance.
- Connection error: verify Node, `npm ci`, and the absolute launcher path. A stdio server waits for protocol input and does not print a success banner. Client-visible logs go to stderr.
- HTTP 401 means the bearer token is missing or wrong; 403 usually means Host or Origin does not match the configured public URL. Ask the endpoint operator to verify configuration.

Some browsers may expose the site's separate `read_avatar_recipe` and `configure_avatar_recipe` WebMCP tools. They depend on browser support and configure the editor only; they do not save/download exports. The server tools above provide the documented file export workflow.

## HTTP and HTTPS deployment

[GitHub Pages serves static files](https://docs.github.com/en/pages/getting-started-with-github-pages/what-is-github-pages) and cannot run this Node process. Keep the Design website there. A shared MCP endpoint needs a running host or container with persistent export storage and TLS, such as an existing server behind an HTTPS reverse proxy. No shared endpoint is currently published; `https://your-host.example/mcp` below is a placeholder, not a working server. Do not use the Pages website URL as an MCP endpoint.

Set a random secret of at least 32 characters in `SPRITEWORKS_MCP_TOKEN`, then run:

```powershell
node tools/spriteworks-mcp.mjs --transport http --host 127.0.0.1 --port 5180 --public-url https://your-host.example/mcp
```

Configure the client with endpoint `https://your-host.example/mcp` and header `Authorization: Bearer <your token>`. Store the secret in the host/client's secret configuration, never in Git. HTTP mode refuses to start without it. `/healthz` reports readiness without exposing assets. Requests validate Host and Origin. The endpoint supports modern MCP and the 2025 stateless Streamable HTTP compatibility flow.

The Node process serves HTTP. The reverse proxy must terminate HTTPS, preserve the public Host header, forward `/mcp`, allow responses up to the export-resource size, and permit tool calls to run for at least 120 seconds. A token grants access to this instance's complete catalog and exports; use separate instances/output volumes/tokens for separate trust groups. This is configured bearer authentication, not a multi-user OAuth service.

For container hosts, build from the repository root using `docker build -f mcp/Dockerfile -t spriteworks-mcp .`. Mount persistent writable storage at `/data`, supply the token through the platform's secret manager, and pass `--public-url https://your-host.example/mcp` as container arguments. The image keeps the rendering process unprivileged. No hosted service, DNS, certificate or public access is provisioned by these files.

## Validation

`npm run typecheck` includes the MCP sources. `npm test` includes actual stdio and authenticated HTTP client exchanges, artifact/resource reads, all advertised output formats, geometry/color/padding checks, animation timing, atlas coordinates, invalid-input rejection and worker cancellation. These checks do not require a browser or cloud credentials.

## Live fits and reusable layouts

`list_assets` and `inspect_asset` include `liveRigFit` for parts and anatomy. A non-null entry identifies a reviewed preview/export fit, its slot and fitted outfit contexts. The app independently selects which items have achievement unlocks. A null entry still allows normal static composition when the asset is compatible with the selected body. The [Fitted poses page](https://fictional-adventure-r2pkkev.pages.github.io/rig/native.html) shows and exports those appearances. [Action fits](https://fictional-adventure-r2pkkev.pages.github.io/rig/combat.html) previews combat, carrot breaks, coffee sips, magnifier inspection and laptop use. Select `rig` for dressed MCP exports, or `animation` for an authored clip.

The runnable composition examples include a square profile card, a wide header and a multi-character banner. These are optional, editable JSON recipes. Copy a recipe, replace the asset IDs and colors, adjust canvas dimensions and layer positions together, then call `compose_image`. Each export also saves its exact `recipe.json` for reuse.

## Client configuration

Replace both absolute paths for the machine running your MCP client. If Node is not on that client's PATH, use its absolute executable path as `command`. This is the common `mcpServers` JSON shape; clients with other configuration formats need the same command and arguments.

```json
{
  "mcpServers": {
    "spriteworks": {
      "command": "node",
      "args": [
        "/absolute/path/to/spriteworks/tools/spriteworks-mcp.mjs",
        "--output-dir",
        "/absolute/path/to/generated-assets"
      ]
    }
  }
}
```

## Runnable examples

These are MCP tool calls, not HTTP request bodies. Discover and inspect the selected IDs first. Each example is exercised by the documentation integration test.

### Dressed astronaut walking animation

```json
{
  "name": "export_asset",
  "arguments": {
    "asset": {
      "kind": "rig",
      "id": "walk",
      "parts": [
        "clothes-15-astronaut-suit",
        "headwear-18-astronaut-helmet-1",
        "props-36-football"
      ]
    },
    "format": "apng",
    "name": "astronaut-walk",
    "canvas": {
      "width": 160,
      "height": 96,
      "padding": 4
    },
    "style": {
      "color": "#70F149",
      "background": "transparent"
    },
    "fps": 10
  }
}
```

### Lab coat walking sprite sheet

```json
{
  "name": "export_sprite_sheet",
  "arguments": {
    "assets": [
      {
        "kind": "rig",
        "id": "walk",
        "parts": [
          "clothes-13-lab-coat",
          "headwear-17-top-hat",
          "props-07-briefcase"
        ]
      }
    ],
    "format": "zip",
    "name": "lab-coat-walk",
    "cell": {
      "width": 96,
      "height": 96,
      "padding": 4
    },
    "columns": 4,
    "gap": 4,
    "margin": 8
  }
}
```

### Cyan logo with asymmetric spacing

```json
{
  "name": "export_asset",
  "arguments": {
    "asset": {
      "kind": "logo",
      "id": "fuzze-logo"
    },
    "format": "png",
    "name": "cyan-logo",
    "canvas": {
      "width": 1200,
      "height": 400,
      "padding": {
        "top": 40,
        "right": 80,
        "bottom": 40,
        "left": 80
      },
      "alignX": "left",
      "fit": "contain"
    },
    "style": {
      "color": "#03E7F3",
      "background": "transparent"
    }
  }
}
```

### Purple bunny with a pirate hat

```json
{
  "name": "export_asset",
  "arguments": {
    "asset": {
      "kind": "bunny",
      "base": "original",
      "parts": [
        "headwear-08-pirate-hat"
      ]
    },
    "format": "svg",
    "name": "pirate-bunny",
    "canvas": {
      "width": 512,
      "height": 512,
      "padding": 24
    },
    "style": {
      "color": "#A365FF",
      "mirror": true
    }
  }
}
```

### Still pose on a solid background

```json
{
  "name": "export_asset",
  "arguments": {
    "asset": {
      "kind": "pose",
      "id": "hop-jump-015"
    },
    "format": "jpg",
    "name": "jump-pose",
    "canvas": {
      "width": 320,
      "height": 240,
      "padding": 12
    },
    "style": {
      "color": "#70F149",
      "background": "#FFFFFF"
    },
    "quality": 90,
    "dpi": 300
  }
}
```

### Walking animation with ping-pong playback

```json
{
  "name": "export_asset",
  "arguments": {
    "asset": {
      "kind": "animation",
      "id": "walk"
    },
    "format": "gif",
    "name": "walking-bunny",
    "canvas": {
      "width": 192,
      "height": 144,
      "padding": 8
    },
    "style": {
      "color": "#03E7F3"
    },
    "fps": 12,
    "playback": "ping-pong"
  }
}
```

### Walking sprite atlas with cell padding and gutters

```json
{
  "name": "export_sprite_sheet",
  "arguments": {
    "assets": [
      {
        "kind": "animation",
        "id": "walk"
      }
    ],
    "format": "zip",
    "name": "walk-atlas",
    "cell": {
      "width": 96,
      "height": 96,
      "padding": 6
    },
    "columns": 4,
    "gap": 4,
    "margin": 8,
    "fps": 12
  }
}
```

### Agent-authored vector geometry

```json
{
  "name": "export_asset",
  "arguments": {
    "asset": {
      "kind": "svg",
      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><rect x=\"10\" y=\"10\" width=\"80\" height=\"80\" rx=\"12\" data-role=\"body\" fill=\"#70F149\"/></svg>"
    },
    "format": "webp",
    "name": "custom-mark",
    "canvas": {
      "width": 256,
      "height": 256,
      "padding": 16
    },
    "style": {
      "colors": {
        "body": "#FF5FE4"
      }
    }
  }
}
```

### Optional composition: a logo and three bunnies on one banner

```json
{
  "name": "compose_image",
  "arguments": {
    "name": "bunny-banner",
    "format": "png",
    "canvas": {
      "width": 1920,
      "height": 1080,
      "background": "#101010"
    },
    "layers": [
      {
        "asset": {
          "kind": "logo",
          "id": "fuzze-logo"
        },
        "x": 120,
        "y": 90,
        "canvas": {
          "width": 650,
          "height": 180,
          "alignX": "left"
        },
        "style": {
          "color": "#70F149"
        }
      },
      {
        "asset": {
          "kind": "bunny",
          "base": "original",
          "parts": [
            "headwear-08-pirate-hat"
          ]
        },
        "x": 130,
        "y": 375,
        "canvas": {
          "width": 440,
          "height": 570,
          "padding": 16
        },
        "style": {
          "color": "#A365FF"
        },
        "rotation": -6
      },
      {
        "asset": {
          "kind": "animation",
          "id": "walk",
          "frame": 2
        },
        "x": 740,
        "y": 405,
        "canvas": {
          "width": 440,
          "height": 540,
          "padding": 16
        },
        "style": {
          "color": "#03E7F3"
        }
      },
      {
        "asset": {
          "kind": "bunny",
          "base": "original"
        },
        "x": 1350,
        "y": 375,
        "canvas": {
          "width": 440,
          "height": 570,
          "padding": 16
        },
        "style": {
          "color": "#70F149",
          "mirror": true
        },
        "rotation": 6,
        "opacity": 0.9
      }
    ]
  }
}
```

### Optional reusable layout: square profile card

```json
{
  "name": "compose_image",
  "arguments": {
    "name": "profile-card",
    "format": "png",
    "canvas": {
      "width": 1080,
      "height": 1080,
      "background": "#000000"
    },
    "layers": [
      {
        "asset": {
          "kind": "logo",
          "id": "fuzze-logo"
        },
        "x": 80,
        "y": 64,
        "canvas": {
          "width": 480,
          "height": 140,
          "alignX": "left"
        },
        "style": {
          "color": "#70F149"
        }
      },
      {
        "asset": {
          "kind": "bunny",
          "base": "original",
          "parts": [
            "headwear-08-pirate-hat"
          ]
        },
        "x": 170,
        "y": 250,
        "canvas": {
          "width": 740,
          "height": 740,
          "padding": 24
        },
        "style": {
          "color": "#A365FF"
        }
      }
    ]
  }
}
```

### Optional reusable layout: wide header

```json
{
  "name": "compose_image",
  "arguments": {
    "name": "wide-header",
    "format": "png",
    "canvas": {
      "width": 1536,
      "height": 512,
      "background": "#101010"
    },
    "layers": [
      {
        "asset": {
          "kind": "logo",
          "id": "fuzze-logo"
        },
        "x": 80,
        "y": 170,
        "canvas": {
          "width": 700,
          "height": 180,
          "alignX": "left"
        },
        "style": {
          "color": "#03E7F3"
        }
      },
      {
        "asset": {
          "kind": "bunny",
          "base": "original"
        },
        "x": 1020,
        "y": 48,
        "canvas": {
          "width": 420,
          "height": 416,
          "padding": 16
        },
        "style": {
          "color": "#70F149",
          "mirror": true
        }
      }
    ]
  }
}
```
