scratch-unified-mcp
scratch-unified-mcp
The first standalone headless Scratch VM over MCP — plus every tool an AI agent needs to build, playtest, and publish Scratch projects without opening a browser.
112 tools. One stdio command. Zero name collisions.
MCP client ──stdio──▶ python3 -m scratch_unified
├── social_* website + social graph (scratchattach)
├── project_* goboscript text-authoring loop
├── spy_* blocks ↔ real Python (ScratchPy)
└── sb3_* block surgery + headless VM test loop (Node sidecar)Why this exists
Three Scratch tool ecosystems each covered part of the loop — website API, block-level editing with a headless VM, Python-to-blocks — but none covered all of it, and switching between three servers with three naming schemes killed momentum. This merges all three behind one transport.
The piece that didn't exist anywhere else: a headless TurboWarp scratch-vm you can drive and inspect over MCP. Load a .sb3, press green flag, click sprites, step frames, read threads, watch variables — all from tool calls, all assertable.
Quickstart
git clone <this-repo> && cd scratch-unified-mcp
pip install -e .
# runtime deps (gitignored reference clones — re-clone to update):
git clone https://github.com/uukelele/scratch-mcp upstream-scratch-mcp
git clone https://github.com/playforge-coding/scratch4js upstream-scratch4js
git clone https://github.com/ZDStudios/scratchpy-studio upstream-scratchpy-studio
# optional, unlocks sb3_* proxy tools:
cd upstream-scratch4js && pnpm install && pnpm build// MCP client config
{ "mcpServers": { "scratch-unified": {
"command": "python3",
"args": ["-m", "scratch_unified"],
"cwd": "/path/to/scratch-unified-mcp",
"env": {
"PYTHONPATH": "/path/to/scratch-unified-mcp",
"SCRATCH_MCP_DATA_DIR": "/path/to/scratch-unified-mcp/.sessions"
// "SCRATCH_MCP_BRIDGE_PORT": "9060" // only if something else holds 9060
},
"timeout": 600000
} } }python3 tests/test_offline.py # 34 checks, no network, no credentials
python3 tests/test_runtime.py # 41 checks, headless VM playtest (see below)
python3 build_tower_game.py # 42-check static gate, regenerates the demo .sb3The headless VM loop
Six core tools, debug tools, and media tools, all proxied to a lazily-spawned Node sidecar (TurboWarp scratch-vm, interpreted mode, stdout muted so MCP framing stays clean):
Tool | What it does |
| Load / write |
| Load the open project into a fresh VM |
| Press green flag (clears bubbles, question, errors) |
| Advance N seconds/frames, paced or flat-out. Returns state + ordered event timeline ( |
| Snapshot now: targets (x/y/vars/lists/costume), monitors, bubbles, question, thread count, errors |
| Keys, mouse position/clicks (stage coords), |
| Every live thread: target, clone flag, starting hat, stack depth, status ( |
| Full monitor table (visible or not) — watch a variable without pixels |
| Exactly one frame + before/after counts + delta (new threads, events that tick) |
| Deterministic PRNG (mulberry32 over |
| Poll-and-diff variable watcher: old/new/changed per key, per-clone capable |
| Recorded pen/sound stub calls since load |
| Pen raster (480×360 software canvas) as PNG base64 + pixel count. Strokes only |
| Offline sound mix as WAV base64: every play at its timer offset, volume/pitch approximate |
Headless gaps are patched, not hidden: distance-based touching fallback (no renderer means every touching returns false upstream), sprite-click shim, broadcast logging via wrapped startHats, interpreter mode to dodge a JIT pickrandom false-alarm. Details: upstream-scratch4js/packages/scratch-mcp/src/runtime.js.
Verification
34 passed, 0 failed (offline: tool surface, sessions, spy round-trip, git diff)
41 passed, 0 failed (runtime: live VM playtest + debug + media tools)
42 passed, 0 failed (static gate on the generator)Tool census
social_* 20 · project_* 18 · spy_* 14 · sb3_* 60 = 112, asserted by the offline suite. Full per-tool reference: docs/IDENTIFIERS.md. Architecture: docs/ARCHITECTURE.md. What the VM research found (and what it corrected): docs/RESEARCH-HEADLESS-RUNTIME.md. Build notes and every trap the game taught us: docs/HANDOFF.md.
Layout
scratch_unified/ the server (this is what runs)
server.py FastMCP app + main()
vendor_uu/ uukelele/scratch-mcp, vendored (social + projects)
spy_loader.py headless ScratchPy import (tkinter stubbed if absent)
spy_tools.py 14 spy_* wrappers, one shared .spy server
node_bridge.py lazy Node sidecar transport (newline-delimited JSON-RPC, TIMEOUT-guarded)
typed_proxy.py typed sb3_* proxies (FastMCP rejects **kwargs)
sb3_extra.py git unpack/pack/diff, studio/remix/favorites, cloud vars
upstream-scratch-mcp/ reference clone (read-only)
upstream-scratch4js/ reference clone + Node sidecar source (runtime dep for sb3_*)
upstream-scratchpy-studio/ reference clone (runtime dep, loaded headless)
tests/ test_offline.py (34) + test_runtime.py (41) + test_stress.py (47)
build_tower_game.py demo game generator + 42-check static gate
tower-castle-defense.sb3 generated demo (84 KB)
docs/ PRD, ARCHITECTURE, IDENTIFIERS, HANDOFF, RESEARCH-HEADLESS-RUNTIMEUpstream dirs are never edited — updates are re-clones. Sessions persist as session IDs only, never passwords. If Node/deps are missing, only sb3_* proxies report unavailable; everything else works.
Credits
uukelele/scratch-mcp (MIT) — social + goboscript core
playforge-coding/scratch4js (MPL-2.0) — sb3/VM/bridge core
ZDStudios/scratchpy-studio (MIT) — blocks↔Python core
TurboWarp/scratch-vm — the headless engine
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/blessed0x/scratch-unified-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server