Skip to main content
Glama

pico-8-mcp

An MCP server that lets an AI assistant build, test and balance PICO-8 games — not just count tokens. Edit a .p8 cart, validate it, simulate a whole 25-minute game headless in about a minute, author sprites / sound effects / music from readable notation, and launch the real PICO-8 to take screenshots.

Works with Claude Code, Claude Desktop, Codex, Cursor, or any other MCP client.

Credits. This is a fork of EBonura/pico8-mcp-server, which provided the original server and the cart analysis tools. Token counting, linting, minification and cart parsing come from shrinko8 by thisismypassport. PICO-8 is made by Lexaloffle. This fork adds the headless simulator, window control, data-section authoring and the workflow skill.

Why

Writing PICO-8 games with an LLM hits the same walls every time: you can't see the game, you can't play it for 20 minutes to check the balance, lint drowns you in false positives about globals, and the sprite / sfx / music sections are raw hex nobody should type by hand. The tools here remove each of those:

Problem

Tool

"Does it crash 12 minutes in?" / "Is sector 4 too hard?"

simulate_cart runs the game loop headless with telemetry

"Does this helper function work?"

run_headless runs any Lua against the cart and returns printh output

"What does it look like?"

run_cart + capture_game return real screenshots; send_keys plays it

"Draw me a sprite / write a jingle"

set_sprite, set_sfx ("c4:2 e4:2 g4:4"), set_music, render_gfx

300 lint warnings about globals

validate_cart hides PICO-8-style global noise by default

Related MCP server: agent-game-engine

Install

Requirements: PICO-8 (any recent 0.2.x), Python 3.11+, uv.

git clone --recurse-submodules https://github.com/nutshot2000/pico-8-mcp.git
cd pico-8-mcp
uv sync

PICO-8 is auto-detected in the usual install locations on Windows, macOS and Linux, or set PICO8_EXE to the executable path. The window tools (run_cart, send_keys, capture_game) are Windows-only for now; everything else, including headless simulation, is cross-platform.

Claude Code

claude mcp add pico8 --scope user -- uv --directory /path/to/pico-8-mcp run server.py

Claude Desktop / other clients

{
  "mcpServers": {
    "pico8": {
      "command": "uv",
      "args": ["--directory", "/path/to/pico-8-mcp", "run", "server.py"]
    }
  }
}

Optional: the workflow skill

skills/pico8-dev/SKILL.md teaches Claude Code the edit → validate → simulate → look loop and the PICO-8 gotchas that bite LLM-written carts (fixed-point overflow, token budget, uninitialised globals). Copy it to ~/.claude/skills/pico8-dev/SKILL.md and it loads automatically whenever PICO-8 comes up.

The workflow

  1. Edit the __lua__ section of the .p8 as plain text.

  2. validate_cart — tokens (8192 max), compressed size, syntax, meaningful lint.

  3. simulate_cart — run it. Catch runtime errors (reported with cart line numbers) and read your own telemetry: levels per minute, kills, enemies on screen, boss HP, hits taken. Use patches to inject a god mode or an autopilot so the game plays itself; stop_when to end on win/death.

  4. set_sprite / set_sfx / set_music, then render_gfx to check the art.

  5. run_cart + capture_game when you actually need to see it.

Example simulate_cart call:

{
  "cart_path": "game.p8",
  "seconds": 1500,
  "setup_lua": "newgame() st=\"play\"",
  "log_every": 60,
  "log_lua": "\"lvl=\"..p.lvl..\" hits=\"..hits..\" enemies=\"..#e..\" boss=\"..(boss and boss.hp or 0)",
  "stop_when": "st==\"win\" or st==\"over\"",
  "patches": [
    {"old": "function hurt_p()\n if p.inv>0 then return end\n",
     "new": "function hurt_p()\n if p.inv>0 then return end\n hits+=1 p.inv=60 do return end\n"}
  ]
}

returns

[1:00] lvl=3 hits=2 enemies=4 boss=0
[2:00] lvl=5 hits=4 enemies=3 boss=0
...
[24:39] STOP lvl=31 hits=33 enemies=0 boss=0

Tools

Analysis (from shrinko8)

  • validate_cart (cart_path, lint="default"|"all"|"none")

  • count_tokens, analyze_cart, search_code, compare_carts, list_carts, minify_cart

  • read_cart (cart_path, section) — code with PICO-8 glyphs intact, plus a summary of the sprites / sfx / music defined

Headless execution (pico8 -x)

  • simulate_cart (cart_path, seconds, setup_lua, log_every, log_lua, stop_when, patches, call_draw, timeout)

  • run_headless (cart_path, driver_lua, timeout)

Window control (Windows)

  • run_cart (cart_path, width=1024, height=1024, restart=true)restart=true closes any PICO-8 already running; pass false when a human may be playing in their own window

  • send_keys (keys)x z c v up down left right enter esc p space r f6, wait:MS, hold:KEY:MS

  • capture_game (keys?, delay_ms, count, interval_ms, max_size) — PNG screenshots of the game area

  • stop_cart

Data authoring

  • set_sprite (cart_path, index, rows, overwrite=false) — rows of hex digits; 8×8 or larger blocks. The sheet is a 16-wide grid of 8×8 cells, so a 16×16 sprite at index also occupies index+1, index+16 and index+17 and is drawn with spr(index, x, y, 2, 2). Place 16×16 sprites at 0, 2, 4 … and 32, 34 … — never at consecutive indices. The tool refuses to write a multi-cell block over cells that already contain pixels (the error explains the stride); overwrite=true forces it, e.g. when redrawing an existing sprite. The result includes the covered cells and the matching spr() call.

  • render_gfx (cart_path, sprites="0-15", scale, size=8) — each index is one 8×8 cell by default, so a 16×16 sprite appears as four labelled quarters; pass size=16 and list the top-left indices to see it whole

  • set_sfx (cart_path, index, notes, speed, wave, volume, effect, loop_start, loop_end)note:len:wave:vol:fx tokens, r = rest, a4 = 440 Hz, range c2..d#7

  • set_music (cart_path, pattern, channels[4], loop_start, loop_end, stop)

PICO-8 facts the tools rely on

  • Numbers are 16.16 fixed point: anything above 32767 wraps negative. A frame counter overflows at 18:12 and for i=1,36000 runs zero times. Track time in seconds; keep scores small.

  • Code budget: 8192 tokens, 65535 chars, 15616 compressed bytes.

  • __gfx__ rows are 128 hex chars, __sfx__ rows 168 chars (00 speed loop_start loop_end + 32 × pitch wave vol fx), __music__ rows flags ch0ch1ch2ch3 with 41..44 meaning a muted channel.

  • pico8 -x cart.p8 runs a cart headless; printh goes to stdout. On a runtime error it prints the message and hangs, and if _update/_draw exist it starts the game loop — the server handles both.

  • The screen is always 128×128; -width/-height only scale the window.

Development

uv run python test_p8tools.py            # runs against examples/demo.p8
uv run python test_p8tools.py my.p8      # or your own cart
uv run python test_tools.py              # the analysis-tool smoke tests take the same optional cart argument

server.py registers the MCP tools; p8tools.py holds the simulator, window control and data authoring; shrinko8/ is the analysis submodule. Contributions welcome — a macOS/Linux capture backend and a set_map tool are the obvious next additions.

License

MIT — see LICENSE. shrinko8 is MIT licensed by its author; the original server is © EBonura.

Related MCP Connectors

Related MCP Servers