Skip to main content
Glama

roblox-studio-mcp

An open-source Model Context Protocol (MCP) server that lets AI assistants — Claude Desktop, Claude Code, or any MCP client — drive Roblox Studio: run Luau, build and edit the DataModel, read the scene tree, and manage scripts.

Status: v0.5 — 25 tools, verified in real Studio. Live server-VM eval during playtests, multi-window routing, line-level script editing, tags/attributes, official docs + class reflection, a hardened bridge (auth + version handshake), and a Rojo-based plugin. Still young; contributions welcome!

How it works

Roblox Studio cannot open a listening socket, so the plugin drives the loop by long-polling a tiny local HTTP bridge that ships inside the MCP server:

┌────────────┐   MCP (stdio)   ┌──────────────────┐   HTTP long-poll   ┌──────────────┐
│ Claude /   │ ◀─────────────▶ │ roblox-studio-mcp │ ◀────────────────▶ │ Studio plugin │
│ MCP client │   JSON-RPC      │  (server+bridge)  │   /poll  /result   │   (Luau)      │
└────────────┘                 └──────────────────┘                    └──────────────┘
  1. Your MCP client calls a tool (e.g. create_instance).

  2. The server queues a command; the plugin picks it up via GET /poll.

  3. The plugin runs it in Studio and posts back via POST /result.

  4. The server returns the result to the client.

Everything stays on 127.0.0.1 — nothing is exposed to the network.

Related MCP server: Roblox Studio MCP Assistant

Tools

Tool

What it does

get_instances

List connected Studio instances and their peers (edit/server).

run_luau

Execute Luau in the edit VM; captures return values and print/warn output.

eval_server_runtime

Execute Luau in a running playtest's server VM (shared require cache).

create_instance

Create an Instance and parent it under a path.

set_properties

Set properties on an existing instance.

delete_instance

Delete an instance (undoable — re-parents to nil, not Destroy).

get_tree

Return the DataModel hierarchy as nested JSON.

get_properties

Read properties from an instance.

get_selection

Return the instances the user has selected in Studio.

read_script

Read a script's source with line numbers; supports line ranges + auto-truncation.

edit_script

Replace a script's whole source via ScriptEditorService (editor-safe, undoable).

edit_script_lines

Replace an inclusive line range with new text (editor-safe, undoable).

insert_script_lines

Insert text before a given line (append with lineCount+1).

delete_script_lines

Delete an inclusive line range.

grep_scripts

Search script sources under a path; returns { path, line, text } matches.

add_tag / remove_tag

Add/remove a CollectionService tag on an instance (undoable).

get_tags

List an instance's CollectionService tags.

get_tagged

Return every instance carrying a tag (optionally under a root).

set_attribute / delete_attribute

Set/remove an instance attribute (undoable).

get_attributes

Return all attributes on an instance.

get_roblox_docs

Fetch official engine reference docs as markdown (server-side; no Studio needed).

get_class_info

List a class's members (properties/methods/events) from the API dump, inheritance-aware.

batch

Run several commands as one all-or-nothing transaction (single undo).

Mutating tools are wrapped in a ChangeHistoryService recording, so every AI action is a single Ctrl+Z in Studio.

Multiple Studio windows

Several open places can connect to the same server at once. Every plugin-backed tool accepts an optional instance_id to pick which window it runs in:

  • One window openinstance_id is optional; calls route to it.

  • Several open, no instance_id → the call returns a structured error listing the connected windows, so the assistant picks one and retries (it never guesses).

  • instance_id given → matched against each window's instanceId or its raw sessionId.

Call get_instances to see what's connected. Each instance is a place (instanceId is place:<placeId>, or anon:<uuid> for an unpublished place) and lists its live peers by role: edit is the plugin, and server appears during a playtest (see Runtime eval). A peer that stops polling is dropped after ~30s.

Runtime eval (live debugging)

eval_server_runtime runs Luau inside the server VM of a running playtest, sharing that VM's require cache — so you can read and change live runtime state (a service's in-memory tables, a match in progress), which run_luau (edit VM) cannot see. It returns { returned, output } just like run_luau.

How it works: the plugin injects a small peer script into ServerScriptService when you Connect; during a playtest that script runs in the server VM, connects to the bridge as a server peer, and evaluates what eval_server_runtime sends. Check get_instances for a server peer to confirm a playtest is live.

Two one-time prerequisites (both are developer settings the plugin cannot set for you):

  1. Allow HTTP Requests — Game Settings ▸ Security. The server VM needs this to reach the bridge.

  2. ServerScriptService.LoadStringEnabled = true — select ServerScriptService in the Explorer and tick LoadStringEnabled in the Properties panel. Runtime eval uses loadstring; this property is not script-settable.

eval_server_runtime executes arbitrary Luau in your live game — it is blocked by read-only mode, and you should only enable it for trusted clients.

Install

1. Build the server

git clone https://github.com/EmirCobanOfficial/roblox-studio-mcp.git
cd roblox-studio-mcp
npm install
npm run build

2. Build & install the Studio plugin

The plugin is a Rojo project under plugin/ — its source lives as separate modules in plugin/src/ (see Plugin layout). Build it into a model file and drop that into your local Plugins folder:

# Install Rojo once: https://rojo.space/docs/v7/getting-started/installation/
rojo build plugin/default.project.json -o RobloxStudioMCP.rbxm
  • In Studio: Plugins tab ▸ Plugins Folder — drop RobloxStudioMCP.rbxm there.

  • Restart Studio (or right-click the Plugins pane ▸ Rescan).

You'll get an MCP toolbar with Connect and Read-only buttons.

Working on the plugin? Edit the modules under plugin/src/ and rebuild — or run rojo serve with the Rojo Studio plugin for live sync during development. Studio does not hot-reload an installed plugin, so after rebuilding the .rbxm you must fully restart Studio (a Rescan is not always enough).

3. Point your MCP client at the server

Claude Desktop — add to claude_desktop_config.json:

{
  "mcpServers": {
    "roblox-studio": {
      "command": "node",
      "args": ["/absolute/path/to/roblox-studio-mcp/dist/index.js"]
    }
  }
}

Claude Code:

claude mcp add roblox-studio -- node /absolute/path/to/roblox-studio-mcp/dist/index.js

4. Connect

Open a place in Studio, click MCP ▸ Connect, then ask your assistant to do something like "create a red neon part 10 studs above the baseplate."

HTTP requests must be allowed. The plugin tries to set HttpService.HttpEnabled = true for you. If requests are blocked, enable Game Settings ▸ Security ▸ Allow HTTP Requests and reconnect.

Encoding Roblox datatypes

JSON has no Vector3, so property values may be plain JSON, or a tagged object { "$type": ..., "value": ... }:

Roblox type

JSON

number/string/bool

5, "hi", true

Vector3

{ "$type": "Vector3", "value": [0, 10, 0] }

Vector2

{ "$type": "Vector2", "value": [1, 2] }

Color3 (0–1)

{ "$type": "Color3", "value": [1, 0, 0] }

UDim

{ "$type": "UDim", "value": [0.5, 20] }

UDim2

{ "$type": "UDim2", "value": [0.5, 20, 0.5, 0] }

CFrame

{ "$type": "CFrame", "value": [x,y,z, ...12 components] }

BrickColor

{ "$type": "BrickColor", "value": "Bright red" }

EnumItem

{ "$type": "EnumItem", "enum": "Material", "name": "Neon" }

Instance ref

{ "$type": "Instance", "path": "game.Workspace.Part" }

Reads (get_properties, get_tree, run_luau return values) come back in the same tagged form.

Example

// create_instance
{
  "className": "Part",
  "parent": "game.Workspace",
  "name": "GlowBlock",
  "properties": {
    "Anchored": true,
    "Position": { "$type": "Vector3", "value": [0, 10, 0] },
    "Color":    { "$type": "Color3", "value": [1, 0, 0] },
    "Material": { "$type": "EnumItem", "enum": "Material", "name": "Neon" }
  }
}

Transactions (batch)

batch runs a list of commands in order as one transaction: all their mutations collapse into a single Ctrl+Z, and if any command fails, every change made earlier in the batch is reverted (via a cancelled ChangeHistoryService recording) and an error is returned. On success you get back each command's result, in order.

// batch
{
  "commands": [
    { "type": "create_instance", "args": { "className": "Folder", "parent": "game.Workspace", "name": "Arena" } },
    { "type": "create_instance", "args": { "className": "Part", "parent": "game.Workspace.Arena", "name": "Floor",
        "properties": { "Anchored": true, "Size": { "$type": "Vector3", "value": [64, 1, 64] } } } },
    { "type": "set_properties", "args": { "path": "Workspace.Arena.Floor",
        "properties": { "Color": { "$type": "Color3", "value": [0.2, 0.2, 0.2] } } } }
  ]
}

If, say, the third command referenced a bad path, the Arena folder and Floor part from the first two would be rolled back — nothing is left half-built. batch cannot be nested.

Script surgery

For anything beyond a whole-file rewrite, work by line — it's far cheaper than shipping entire scripts back and forth, and it keeps unrelated code untouched.

  • read_script returns numberedSource (1: local x = …), lineCount, and sourceLength. Pass startLine/endLine to read a slice; without a range a large script is truncated to the first 400 lines with a note on how to page.

  • grep_scripts finds where something lives — { path, line, text } matches across every script under a path (literal by default; plain=false for a Luau pattern).

  • edit_script_lines / insert_script_lines / delete_script_lines change a line range, insert before a line (lineCount+1 appends), or remove a range. All are editor-safe (ScriptEditorService) and undoable, and are blocked by read-only mode.

Typical loop: grep_scripts to locate → read_script a range for context → edit_script_lines the exact lines.

Tags & attributes

add_tag / remove_tag / get_tags / get_tagged wrap CollectionService, and set_attribute / get_attributes / delete_attribute cover instance attributes (values use the same datatype encoding as properties). The mutating ones are undoable and blocked by read-only mode.

Reference data

Two tools pull official Roblox data server-side — they don't touch the Studio bridge, so they work even when nothing is connected:

  • get_roblox_docs returns an engine reference page as markdown (from create.roblox.com) so the agent can check real API semantics instead of guessing. Names are case-sensitive PascalCase; categories: classes (default), enums, datatypes, libraries, globals.

  • get_class_info lists a class's properties (with value types), methods, events, and callbacks from the official API dump, walking the full inheritance chain — so get_class_info Part includes Anchored (from BasePart), not just Part's own members. Results are cached for a day.

// grep_scripts → read_script → edit_script_lines
{ "pattern": "PlayerAdded" }                                   // find it
{ "path": "ServerScriptService.Main", "startLine": 40, "endLine": 60 }  // read around it
{ "path": "ServerScriptService.Main", "startLine": 47, "endLine": 47,
  "source": "\tprint(\"Welcome, \" .. player.DisplayName)" }   // patch one line

Paths

Paths are resolved from game. Both dots and slashes work, and the leading game is optional:

game.Workspace.Baseplate
Workspace/Baseplate
ServerScriptService.Main

The first hop off game is resolved with game:GetService(...) when possible.

Plugin layout

The Studio plugin is a Rojo project. plugin/default.project.json maps plugin/src/ into a single Script (the plugin) with ModuleScript children:

plugin/
├── default.project.json   # Rojo project → builds RobloxStudioMCP.rbxm
└── src/
    ├── init.server.lua     # entry: dispatch, poll loop, toolbar (Connect / Read-only)
    ├── Config.lua          # server URL, reconnect delay, mutating-command set
    ├── Paths.lua           # "game.Workspace.Part" → Instance resolution
    ├── Encoding.lua        # Luau ⇄ JSON "$type" tagged datatypes
    └── Handlers.lua        # one function per command (run_luau, create_instance, …)

init.server.lua owns the stateful/IO pieces (networking, undo recording, read-only gating, toolbar), and Handlers are otherwise pure command logic.

Configuration

Env var

Default

Description

ROBLOX_STUDIO_MCP_PORT

44755

Port for the local plugin bridge.

ROBLOX_STUDIO_MCP_READONLY

(unset)

When set to anything other than "", 0, or false, blocks all mutating tools. See Read-only mode.

ROBLOX_STUDIO_MCP_TOKEN

(unset)

Optional shared secret required on every plugin request. Also read from ~/.roblox-studio-mcp/token. See Security.

If you change the port, also update SERVER_URL at the top of the plugin.

Read-only mode

A safety switch that blocks every mutating tool — create_instance, set_properties, delete_instance, edit_script, and batch — while leaving the read tools (get_tree, get_properties, get_selection, read_script, run_luau) available. Useful when you want the assistant to inspect a place but not change it. It's enforced in two independent places — either one blocks a mutation:

  • Server (launch-time lock). Start the server with ROBLOX_STUDIO_MCP_READONLY=1. Mutating tools are refused before the request ever reaches Studio, and the server logs READ-ONLY mode on startup.

  • Plugin (runtime toggle). Click MCP ▸ Read-only in Studio. While active, the plugin refuses mutating commands no matter what the client sends. Reads still work. This is authoritative — it's enforced at the point of execution, under the control of whoever is at the Studio session.

Note: run_luau is a read tool for gating purposes, but it can execute arbitrary Luau — including code that mutates the DataModel. Read-only mode does not sandbox run_luau; use the plugin toggle plus your own review if you need a hard guarantee against changes.

Security notes

  • The bridge binds to 127.0.0.1 only.

  • Origin/Host guard (always on). The bridge rejects any request carrying an Origin header or a non-loopback Host — so a web page in your browser can't drive Studio via fetch() (DNS-rebinding / CSRF), while the plugin's plain localhost requests pass.

  • Optional shared-secret token. Set ROBLOX_STUDIO_MCP_TOKEN (or create ~/.roblox-studio-mcp/token) and every plugin request must present it (checked with a constant-time compare). Give the plugin the same value once, from the Studio command bar:

    plugin:SetSetting("MCPToken", "your-token-here")

    Leave it unset (the default) and the loopback + Origin/Host guard still apply.

  • run_luau executes arbitrary Luau in the Studio/plugin context. Only connect Studio to an MCP client you trust, and review what the assistant does.

  • Mutations are undoable via Studio's history.

  • Read-only mode blocks the dedicated mutating tools when you want look-but-don't-touch access.

Version handshake

On every poll the plugin sends its version and a per-session id; the server replies with its own version and whether it has seen this session before. The plugin prints a Studio warning if the two versions disagree (so a stale plugin or server is obvious), and automatically re-registers if it detects the server was restarted underneath it.

Troubleshooting

  • "No Roblox Studio plugin is connected." The plugin does not auto-connect when Studio starts — click MCP ▸ Connect on the toolbar after opening your place. The button is a toggle; make sure it's active.

  • Edited the plugin but nothing changed? Rebuild the .rbxm from plugin/src/ (rojo build …) and note that Studio doesn't hot-reload local plugins — fully restart Studio, then click Connect again.

  • HTTP requests blocked. Enable Game Settings ▸ Security ▸ Allow HTTP Requests and reconnect (the plugin also tries to set this for you).

  • Deletes should be undoable. delete_instance re-parents to nil inside a ChangeHistoryService recording, so a single Ctrl+Z restores the instance. (Instance:Destroy() would lock the instance and make the delete unrecoverable.)

  • Mutations keep getting refused ("Read-only mode is enabled")? Either the MCP ▸ Read-only toggle is active in Studio, or the server was started with ROBLOX_STUDIO_MCP_READONLY set. See Read-only mode.

  • "Version mismatch" warning in Studio output? The plugin and server builds differ — rebuild/reinstall the plugin (rojo build …, restart Studio) or restart the server so both are the same version.

  • "Server rejected the connection (HTTP 401/403)"? A token is configured on the server but the plugin's MCPToken setting is missing or wrong — set it to match ROBLOX_STUDIO_MCP_TOKEN (see Security).

  • eval_server_runtime says "No live server peer"? No playtest is running — press Play (F5) and check get_instances for a server peer.

  • eval_server_runtime fails to compile / "LoadStringEnabled"? Tick ServerScriptService.LoadStringEnabled in the Properties panel, and make sure Allow HTTP Requests is on. See Runtime eval.

Roadmap

Done

  • Verified in real Studio

  • Selection-aware tools (operate on the current Studio selection)

  • Output/print capture for run_luau

  • Batch/transaction commands

  • Optional read-only mode

  • Rojo-friendly project layout for the plugin

  • Multi-window instance routing

  • Live server-VM eval during playtests (eval_server_runtime)

Planned

  • Client-VM eval + multiplayer peer roles

  • Runtime log capture per peer

Contributing

Issues and PRs welcome. Run npm run build (server) before pushing; CI builds on Node 20. When changing the plugin, edit the modules in plugin/src/ and rebuild the .rbxm with rojo build plugin/default.project.json -o RobloxStudioMCP.rbxm.

License

MIT © Emir Coban

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
4Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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/EmirCobanOfficial/roblox-studio-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server