Skip to main content
Glama

Studio Live

Real-time Roblox Studio for AI agents. One MCP server, nine tools, an agent runtime that lives inside Studio.

CI MIT License Node ≥ 20 MCP

Install · Quick start · Tools · Works with · Docs · Security

Studio Live connects an AI coding agent to a running Roblox Studio over a single WebSocket. Instead of one tool call per property, the agent ships whole Luau programs against a resident API, gets errors and assertions pushed into its context, keeps playtests alive while it hot-patches code, reaches Open Cloud for the open place, and asks a vision sidecar visual questions that come back as text.

  • Fast where it matters — 16 ms round trip (one Heartbeat frame), a hot-patch in a live playtest in ~17 ms, screenshots in ~30 ms even while Studio is minimised. Every number is measured; see Why it feels fast.

  • One undo step per program — an edit-DataModel run is one Ctrl+Z for the human and rolls back on error.

  • Push, not poll — errors, assertions, milestones, playtest, peer and job events arrive as they happen.

  • Playtests stay up — install controllers that run at Heartbeat in-engine, hot-patch scripts, push instances, run_until a predicate — no stop/start per change.

  • Multi-agent, multi-Studio — several agents share one Studio with FIFO writes; several Studio windows (server + N clients) form a multiplayer playtest.

  • Geometry is enforced — overlapping parts and "parts in parts" are detected on every edit and can be rejected outright.

  • Any MCP client — Claude Code, Codex CLI, Cursor, Claude Desktop, or a plain shell.

The design record is in docs/architecture.md, the wire contract in docs/protocol.md, the engine measurements it rests on in docs/live-measurements.md. Agents should read docs/agent-guide.md; the cloud and look tools have their own pages, docs/cloud.md and docs/vision.md.

Works with any MCP client

It is a plain stdio MCP server. Nothing in the plugin, the protocol or the tools is tied to one client; only the way pushed events reach the model differs.

Client

Tool calls

Push events

look (vision)

Claude Code

stdio MCP (claude mcp add …)

its Monitor tool subscribes to ws://127.0.0.1:47800/events

Anthropic API credential, or your Claude Code login

Codex CLI · Cursor · Claude Desktop · any MCP client

stdio MCP

the events tool (long-poll), or subscribe to /events yourself

Anthropic API credential, or a claude CLI on PATH

Shell, scripts, other agents

studio-live call <tool> or POST /rpc

/events WebSocket

same

Several clients can run at once — the first bridge owns Studio, later ones proxy through it automatically. look is the one tool with a model behind it and today it only speaks to Anthropic models (the backend is the VisionProvider interface in bridge/src/vision/types.ts). Everything else works with no AI-vendor credential at all.

Related MCP server: roblox-studio-mcp

Why it feels fast

Every number below was measured on one machine (Studio 0.738, Windows 11); see docs/live-measurements.md.

Cost

Existing connectors

Studio Live

Measured

Transport wait per hop

community plugin polls every 0.5 s: mean 250 ms, worst 500 ms, 2 req/s dispatch

one persistent WebSocket, push both ways

echo RTT p50 16.5 ms, max 17.7 ms (= one Heartbeat frame); 200 requests fired in one frame all answered in the next

Changing code in a playtest

stop → edit → start ≈ 2.7 s plus two LLM turns

the playtest stays up; Script.Source written + Disabled toggled

hot-injected Script/LocalScript ran within 16.6 / 16.9 ms

Screenshot

in-engine CaptureService: 649 / 592 ms (≈1.5 fps), never fires while minimized

Win32 PrintWindow on the Studio HWND from the bridge

28–44 ms for 1734×1399, works while occluded; ≈100 ms round trip including bicubic resize to 1024 px and JPEG

Structured observation

one tree read or screenshot per LLM turn

pushed events + diff since a cursor

full 2 381-instance snapshot: walk 2.6 ms + JSON 0.55 ms = 114 KB

LLM turns per task

2–10 s per micro-tool, ten tools for "walk to the door and try it"

one turn ships a Luau program or a controller that runs at Heartbeat in-engine

the turn itself cannot be removed, only the count

The turn is the real cost, so the design (1) moves closed loops into Studio (controllers, run_until predicates, in-engine assertions), (2) makes each turn do more (programs against a resident S API, saved skills), and (3) streams observations to the agent instead of making it ask.

Requirements

  • Windows 10/11. The bridge and the plugin are platform-neutral Node and Luau, but the screenshot worker is Win32 (PrintWindow) and install / twin look up Windows paths, so macOS is not supported yet.

  • Node.js ≥ 20.

  • Roblox Studio with Load User Plugins In Run Modes on (File → Studio Settings → Studio).

  • Optional: an Anthropic credential or a Claude Code login for look; a Roblox Open Cloud API key for cloud.

Install

Full walkthrough with the reasons behind each step: docs/install.md.

git clone https://github.com/gurmyd/roblox-studio-live.git
cd roblox-studio-live
npm install
npm run build            # tsc + copy worker.ps1 into dist + pack plugin/bootstrap.luau -> dist/StudioLive.rbxmx
npm run install:studio   # copies the plugin into %LOCALAPPDATA%\Roblox\Plugins and prints the next steps

Then, once: restart Roblox Studio. The edit DataModel loads plugin files only at start; after that the runtime is pushed by the bridge on every connect, so updating this package never needs another restart.

Claude Code

The install step prints the exact line with the absolute path:

claude mcp add studio -- node "<absolute path>\dist\bridge\cli.js" serve

Recommended .claude/settings.json, so tool calls and the push monitor run without prompts:

{ "permissions": { "allow": ["mcp__studio__*", "Monitor"] } }

Codex CLI

~/.codex/config.toml:

[mcp_servers.studio]
command = "node"
args = ["C:\\path\\to\\roblox-studio-live\\dist\\bridge\\cli.js", "serve"]
# tool calls longer than Codex's tool timeout return a job handle; use job { action: "wait" }

Cursor / Claude Desktop / other JSON-configured clients

{ "mcpServers": { "studio": { "command": "node", "args": ["C:\\path\\to\\roblox-studio-live\\dist\\bridge\\cli.js", "serve"] } } }

Quick start

In a Claude Code session with Studio open on a place:

  1. Arm push once — events then arrive in context without asking: Monitor({ ws: { url: 'ws://127.0.0.1:47800/events' }, persistent: true }) (other clients: call events { since: 0, timeout_ms: 25000 } when you want a batch)

  2. observe { what: "status" } — confirms the hub is connected, which DataModels are alive, capabilities.

  3. run { dm: "edit", undo_label: "agent: first part", code: "return S.path(S.part{ Name='Hello', Size=Vector3.new(4,1,4), CFrame=CFrame.new(0,3,0) })" } — one undo step the human can Ctrl+Z.

  4. playtest { action: "start", mode: "play" }, then playtest { action: "install", dm: "client:1", name: "walker", code: "<controller>" } — the controller plays and reports assertions as events while you keep editing.

Worked examples for every acceptance test (T1–T4) are in docs/agent-guide.md. A record of six agents building a playable game concurrently through the bridge is in docs/multi-agent-build-report.md.

Tools

Tool

Purpose

run

Execute a Luau program against the resident S API in edit (one undo step, rolled back on error) or server/client:N (ephemeral). code or code_file (the bridge reads the file — no shell/JSON escaping touches Luau). Returns value, captured output, change counts, duration, plus warnings (a :Destroy( call in an edit-DM program is not undoable) and unwritable (properties the plugin VM cannot set).

observe

Read-only: status, tree (an explicit root is always returned, n: 0 when empty), props, find, diff (since cursor), logs (dm: "all" merges every DataModel; play-DM startup prints have real seqs), script (path, from, to line ranges), stats, selection, player, screenshot, windows, geometry, selftest.

playtest

start (play / run / multiplayer with players client Studios) / stop / status / add_players; run_until (predicate evaluated in-engine; predicate_file); install/uninstall/list controllers (code_file; persist: true is stored by the bridge on disk and re-installed on every peer hello — survives runtime, bridge and Studio restarts; an unsaved place, placeId 0, is memory-only); hotpatch a script's source in a live DataModel (source_file); push edit-DM instances into the live test (replace default true: a same-named, same-class sibling at the target is removed first, replaced: n; Terrain, the camera and player characters are never touched, skipped).

input

Human-like input sequences in the play client (keys, clicks, move, look, text, wait, focus; no scroll — an engine limit) through UserInputService:CreateVirtualInput().

events

Long-poll backfill of the event journal (since, kinds, timeout_ms ≤ 50000) — the portable fallback to Monitor push.

skills

Luau program library on disk: list/get/save (source_file)/delete/run; eight read-only builtins ship with the bridge (settle_physics, device_sim, profile_scripts, bulk_attributes, insert_asset, lighting_preset, list_scripts, remote_map), overridable by name.

job

status/cancel/wait/list for long operations that returned a handle.

cloud

Roblox Open Cloud for the open place: datastore / ordered / message / info / asset_upload / luau (server-side execution against the published place). Universe, place and creator ids default from the connected Studio session; the API key is read per call from ROBLOX_OPEN_CLOUD_KEY or <STUDIO_LIVE_HOME>/opencloud.json / .key (docs/cloud.md).

look

Vision sidecar: {question} screenshots Studio and answers in text through a Claude vision model, so no image enters the agent's context; {watch: {question, interval_s, stop_when}} keeps capturing, skips unchanged frames and streams vision events to /events; list / stop (docs/vision.md).

observe, events and look are annotated read-only so Claude Code runs them in parallel with other calls; job is not (cancel rolls back an edit-DM recording). cloud and look are the only tools that leave the machine (openWorldHint: Open Cloud, the Claude API). With two Studio windows connected, write tools require session and reads carry a session_note.

look reaches its vision model one of two ways. With an Anthropic API credential in the bridge's environment (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, or an ant auth login profile) it calls the Claude API directly — about 2 s per look. Without one, a logged-in Claude Code install is enough: when claude is on the bridge's PATH the sidecar runs claude -p --output-format json per frame on your subscription (~10–15 s per look, watch interval at least 15 s, usage counted against your plan's limits rather than an API bill). STUDIO_LIVE_VISION_PROVIDER = auto | api | claude-cli chooses (default auto: the API when a credential resolves, else the CLI), and every answer and vision event names the provider that served it — see docs/vision.md.

Programs in play DataModels get the same S API plus S.ensure(path, class, props?) (create-if-missing for roots shared between agents), S.script.get(path, {from, to}) line ranges, and in controllers ctx.pathTo(pos, timeout) (PathfindingService) next to the straight-line ctx.moveTo. Several agents can share one Studio: edit-DM writes queue FIFO (no busy until 500 are waiting; measured live with 30 and 100 concurrent programs), and docs/agent-guide.md has the multi-agent contract they follow.

Geometry is enforced ("parts in parts"): every edit-DM run checks the parts it added or moved through S for intersections with other parts (touching faces are fine) and for Parts parented under Parts, and reports them as geometry + warnings; geometry_policy: "reject" (or STUDIO_LIVE_GEOMETRY_POLICY=reject) rolls such a run back with geometry_violation, observe { what: "geometry" } audits any subtree, and S.placeOn / S.fits / S.overlaps make correct placement one line — see the agent guide's "Geometry rules (enforced)".

Command line

Everything the tools do is also reachable from a shell through the running bridge (POST /rpc):

studio-live serve                                   # the bridge + MCP server on stdio (what Claude Code launches)
studio-live install                                 # install the bootstrap plugin (STUDIO_LIVE_PORT baked in)
studio-live status                                  # GET /status of the running bridge (sessions, journal, jobs)
studio-live call <tool> [json | -] [--raw] [--port N] [--timeout ms]
studio-live call run --code-file .\build.luau --args-file .\build.args.json
studio-live call playtest '{"action":"hotpatch","dm":"server","path":"ServerScriptService.Main"}' --source-file .\Main.server.luau
studio-live call playtest '{"action":"run_until","dm":"server"}' --predicate-file .\ready.luau
studio-live sync <dir> [--pull] [--once] [--no-hotpatch] [--port N]
studio-live twin <place.rbxl> [--port N] [--timeout ms] [--exe C:\...\RobloxStudioBeta.exe]
  • call performs one tool call and prints the tool's text (--raw: the whole MCP result JSON); exit code 1 when the result isError or no bridge answers. --args-file <json> supplies the whole argument object; --code-file, --source-file, --predicate-file read a Luau file (UTF-8, BOM stripped) into code / source / predicate — the supported way to hand Luau to the tools from scripts and subagents, because shell heredocs turn \n inside Luau strings into real newlines (the resulting Malformed string error says so).

  • sync mirrors a folder of .luau files into the open place (or the place into the folder with --pull) — docs/sync.md.

  • twin launches a second Roblox Studio on a local place file (the newest RobloxStudioBeta.exe under %LOCALAPPDATA%\Roblox\Versions\version-*\, then a per-machine install under %ProgramFiles(x86)%\Roblox\Versions; --exe <path> or STUDIO_LIVE_STUDIO_EXE skips the scan; spawned detached with the .rbxl as its argument), waits up to 90 s (--timeout ms) for the new session to show up in GET /status, and prints its session id and place. A Studio that cannot start, or exits before connecting, is reported immediately (exit 1). **Multiple sessions:** each Studio process is its own session; while more than one is connected, pass session: "<guid or unique prefix>" on tool calls — reads default to the active session and say so (session_note), writes (run, playtest, input, skills run) refuse to guess. Naming a session once makes it the active one.

Troubleshooting

Screenshot fails with minimized. PrintWindow cannot render a minimized window (Studio suspends its render loop). By default the bridge un-minimizes it without taking focus (ShowWindow(SW_SHOWNOACTIVATE) and a SetWindowPos to the bottom of the Z-order) and waits 400 ms; pass restore: false to refuse instead. A window that was maximized before being minimized comes back at its normal size — that is a Win32 limit of non-activating restores. Occluded windows capture fine; you do not need Studio in front.

Screenshot fails with no_window. No visible top-level window of a RobloxStudioBeta process has "Roblox Studio" in its title: Studio is not running, is still on the splash screen, or title_match did not match (the message lists the titles it saw). With several Studio processes the foreground one wins, else the topmost in Z-order; within a process the largest window wins (the main window over floating docks). observe { what: "windows" } lists them; pass hwnd from that list to pin one. The first screenshot after a bridge start can take a few seconds while the PowerShell worker compiles its Win32 shim; the bridge warms it up in the background and does not count that time against the 10 s capture timeout.

playtest start says no_peer. The play server/client DataModels never said hello. Open File → Studio Settings → Studio and turn Load User Plugins In Run Modes on (Faster Play Solo disables user plugins in test mode by default), and check that StudioLive.rbxmx is installed and Studio was restarted after installing it. observe status shows peers once they connect.

Port 47800 is in use. The bridge listens on 127.0.0.1:47800 (STUDIO_LIVE_PORT overrides; Studio connects to the literal 127.0.0.1). A second MCP process on the same machine must not fight for the socket: it detects the primary and runs in proxy mode, forwarding tool calls over POST /rpc to the primary, which owns the single Studio connection; when the primary exits it first waits for jobs the proxy started, and the proxy then takes the port over on its next call. If the port is held by something else entirely, either free it or pick another: the plugin reads no settings, so set STUDIO_LIVE_PORT in the environment, run npm run install:studio again (it bakes the port into StudioLive.rbxmx), restart Studio once, and register the MCP server with --env STUDIO_LIVE_PORT=<port> (the install output prints the exact line).

syntax_error: Malformed string on code that is valid Luau. The program reached Studio with a real newline inside a quoted string: a shell heredoc or a JSON layer collapsed \\n to \n before the bridge saw it (the bridge itself is escape-clean — its selftest compiles "\n" and "[^\n]+" inside Luau strings). The error message appends the hint "your transport turned \n into a newline — pass code from a file (code_file)" when that is what happened. Write the program to a file and pass code_file / source_file / predicate_file (or studio-live call --code-file); never work around it with string.char(10).

Persisted controllers vanished after a restart / playtest list shows none. Persistence lives in the bridge (<STUDIO_LIVE_HOME>/persist/<placeId>.json, default home ~/.studio-live), not in Studio: plugin:GetSetting returns nil for every key on Studio 0.738. The bridge re-sends the list to the hub on every hello, so entries return when the bridge that stored them (or one sharing its home directory) is running; an entry is removed by uninstall or by a later install of the same name without persist. An unsaved place (placeId 0) has no file: its entries are memory-only and gone after a bridge restart. persist_note instead of persist_file on an install means the bridge could not write the file (read-only home, disk full) — the entry lives in memory for that bridge process only. Two Studios on the same placeId (a twin) share the file, which holds the union of both sessions' lists.

Monitor is unavailable (Claude Code). Monitor is gated off when DISABLE_TELEMETRY / CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is set, on Bedrock/Vertex/Foundry, in non-interactive runs, and in some clients. Use the events tool instead: events { since: <last seq>, timeout_ms: 25000 } long-polls the same journal, and a PostToolBatch hook can attach "events since cursor" to every tool round trip. Also call events whenever a pushed frame shows dropped > 0 or a seq gap.

Nothing happens after editing plugin/bootstrap.luau. Studio does not load a newly written plugin file while running. Only the bootstrap lives on disk; iterate on runtime code through the bridge (pushed on connect) and restart Studio only when the bootstrap itself changes.

Development

npm run typecheck                 # tsc --noEmit
npm test                          # vitest (tests/**/*.test.ts); capture tests use a fake worker, no Studio needed
npm run build                     # dist/bridge/*.js + worker.ps1 + dist/StudioLive.rbxmx + dist/StudioLive.lua
npm run selftest                  # offline end-to-end: real bridge on a random port + scripts/fake-hub.mjs, PASS/FAIL per protocol check
npm run luau:check                # parse/type-check plugin/**/*.luau with luau-lsp (fetches Roblox definitions once)
npm run fake-hub -- --port 47800  # pretend to be Studio against a running bridge (handshake, events, canned answers)
node scripts/capture-smoke.mjs    # lists Studio windows and captures two frames with timings (Studio must be open)

Layout: bridge/src (Node bridge: MCP server, sessions, journal, jobs, skills, capture/ worker, cloud/, vision/, sync/), plugin/bootstrap.luau (the only code installed into Studio) and plugin/runtime/** (the hub/agent runtime the bridge pushes on connect; see docs/luau-runtime.md), skills/builtin (Luau programs shipped with the bridge), scripts/ (build, packaging, selftest, smoke), docs/, tests/.

The bridge's stdout is the MCP stdio transport; every log line goes to stderr. Screenshots land in %TEMP%\studio-live\frames (newest 200 kept). The rules that keep the live system working (the bootstrap is frozen, tests never bind 47800, keys are never printed) are in AGENTS.md.

Documentation

Page

What it covers

docs/agent-guide.md

How an agent should work with the tools: working style, worked examples, the multi-agent contract, geometry rules, look vs observe

docs/architecture.md

The design record: what was built and why

docs/protocol.md

The wire contract between bridge and plugin: bootstrap, ops, events, the S API, value serialization

docs/luau-runtime.md

The in-Studio runtime that the bridge pushes on every connect

docs/install.md

Install walkthrough with the reason behind each step

docs/cloud.md

The cloud tool: Open Cloud actions, permissions, key setup

docs/vision.md

The look tool: providers, watch mode, costs

docs/sync.md

studio-live sync: mirroring .luau files into and out of a place

docs/live-measurements.md

Engine measurements on Studio 0.738 the design rests on

docs/live-test-results.md

Acceptance-test results against a live Studio, defects found and fixed

docs/multi-agent-build-report.md

Six agents building a playable game concurrently through the bridge

docs/research-brief.md

The architecture brief that preceded the build: what a connector can and cannot make fast

Security

The bridge listens on 127.0.0.1 only and has no authentication: any process on your machine can drive Studio through it, and the plugin runs whatever bundle the bridge sends it. Only look (a screenshot to the Anthropic API) and cloud (your request to Open Cloud) leave the machine. API keys are read from the environment or ~/.studio-live, never returned by a tool, and masked in logs. Nothing saves or publishes a place on its own.

License

MIT. Not affiliated with Roblox Corporation.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to control Roblox Studio by running Luau code, creating and editing instances, reading the scene tree, and managing scripts via an MCP server with a long-polling plugin bridge.
    MIT
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to interact with Roblox Studio via a token-efficient MCP server, live two-way script sync, and zero-friction plugin setup.
    -