Powder Toy MCP
# Powder Toy MCP
An [MCP](https://modelcontextprotocol.io) server that lets an AI client control [The Powder Toy](https://powdertoy.co.uk/) through a small, CLI-style tool surface.
> **Status:** first implementation written. The MCP server is tested end to end against a fake plugin. The Lua plugin is untested inside the game (see Installation).
## How it works
```
MCP client ──stdio──> Node.js MCP server ──TCP──> Lua plugin (inside Powder Toy)
```
- **Lua plugin** — runs inside the game (loaded via `autorun.lua`), uses the in-game Lua API (`sim`, `elem`, `gfx`, `event`) and executes commands on the game thread from a tick hook.
- **MCP server** — a Node.js process exposing two tools, which forward commands to the plugin and return the results (text or images).
## Tools
| Tool | Description |
| --- | --- |
| `pt read_instructions` | Returns usage docs for all available commands |
| `pt execute` | Runs a command against the running game |
## Planned capabilities
- Draw elements on screen (particles, lines, rectangles, fill, walls)
- Play / pause the simulation, step frames
- Get info on objects (particles, elements, areas)
- Get info on the project (save metadata, particle counts, frame)
- Render the screen as PNG and return it for inspection
- Change environment settings (gravity, air, heat, ambient temperature, edge mode, …)
## Requirements
- The Powder Toy 100.x (tested target: `powder-toy` 100.1.400 from the AUR), which embeds LuaJIT 2.1 (Lua 5.1 semantics) and a `socket` library
- Node.js and npm
- Optional: standalone `lua` / `luajit` for testing pure-logic code outside the game
## Installation
**From npm (recommended):**
1. Install the plugin into the game: `npx powder-toy-mcp install-plugin` (copies `autorun.lua` into the game's data folder; add `--force` to overwrite a different `autorun.lua`, or pass a directory to override the location).
2. Register the server with your MCP client, e.g. `claude mcp add pt -- npx -y powder-toy-mcp`.
3. Start the MCP client first, then Powder Toy. The plugin retries the connection about every 2 seconds.
4. In the game, run `pt execute "api sim"` to check which Lua functions exist. The plugin is written from memory of the v100 API, so a few calls may need adjusting.
**From a checkout (development):**
1. `npm install`, then `npm run dev` runs the server (the repo's `.mcp.json` does this for Claude Code). TCP port 8787, override with `PT_PORT`.
2. `ln -s "$PWD/plugin/autorun.lua" "$HOME/.local/share/The Powder Toy/autorun.lua"` so edits to the plugin take effect on restart.
## Notes
- Plugin code must stay compatible with LuaJIT / Lua 5.1 (use `bit.*` rather than bitwise operators).
- All simulation access happens in the plugin, on the game thread, and must never block the game loop.
- Socket availability was found by inspecting the game binary and still needs a runtime check (`require("socket")` in the in-game console, opened with `~`).
See [CLAUDE.md](CLAUDE.md) for the full design notes and environment findings.
TDQS
Scored across 2 tools
The two tools have completely distinct roles: one returns documentation, the other runs commands. There is no realistic way to confuse them, and the descriptions reinforce the required ordering (read_instructions before execute).
Both names are snake_case, but read_instructions follows a verb_noun pattern while execute is a bare verb, and read_instructions is a somewhat unusual verb choice for 'get docs'. Minor deviation only; still predictable and readable.
Two tools is on the thin side, though the pattern is deliberate: a single generic dispatcher plus a docs loader. It avoids a bloated per-command surface, but leaves only one actual capability-bearing tool for the whole domain.
Because execute can invoke any underlying pt command, the surface is nominally broad, and read_instructions is meant to cover discovery. However, there is no structured command enumeration, no typed parameters, and no validation or error contract beyond prose docs, which pushes discovery and correctness entirely onto the agent reading a doc blob.