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: early (v0.1). Works, but expect rough edges. Contributions welcome!
How it works
Roblox Studio cannot open a listening socket, so the plugin drives the loop bylong-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) │
└────────────┘ └──────────────────┘ └──────────────┘
- Your MCP client calls a tool (e.g.
create_instance). - The server queues a command; the plugin picks it up via
GET /poll. - The plugin runs it in Studio and posts back via
POST /result. - The server returns the result to the client.
Everything stays on 127.0.0.1 — nothing is exposed to the network.
Tools
| Tool | What it does |
|---|---|
run_luau |
Execute an arbitrary Luau snippet; captures return values and print/warn output. |
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 the Source of a Script/LocalScript/ModuleScript. |
edit_script |
Replace a script's Source (undoable). |
batch |
Run several commands as one all-or-nothing transaction (single undo). |
Mutating tools are wrapped in a ChangeHistoryService recording, so every AIaction is a single Ctrl+Z in Studio.
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. Install the Studio plugin
Copy plugin/RobloxStudioMCP.server.lua into your local Plugins folder:
- In Studio: Plugins tab ▸ Plugins Folder — drop the file in there.
- Restart Studio (or right-click the Plugins pane ▸ Rescan).
You'll get an MCP toolbar with a Connect button.
Updating the plugin later: Studio does not hot-reload local pluginfile changes. After overwriting the file, fully restart Studio to pick upthe new version (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 dosomething like "create a red neon part 10 studs above the baseplate."
HTTP requests must be allowed. The plugin tries to set
HttpService.HttpEnabled = truefor you. If requests are blocked, enableGame Settings ▸ Security ▸ Allow HTTP Requests and reconnect.
Encoding Roblox datatypes
JSON has no Vector3, so property values may be plain JSON, or a taggedobject { "$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 thesame 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 theirmutations collapse into a single Ctrl+Z, and if any command fails, everychange made earlier in the batch is reverted (via a cancelledChangeHistoryService recording) and an error is returned. On success you getback 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 andFloor part from the first two would be rolled back — nothing is lefthalf-built. batch cannot be nested.
Paths
Paths are resolved from game. Both dots and slashes work, and the leadinggame is optional:
game.Workspace.Baseplate
Workspace/Baseplate
ServerScriptService.Main
The first hop off game is resolved with game:GetService(...) when possible.
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. |
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 leavingthe read tools (get_tree, get_properties, get_selection, read_script,run_luau) available. Useful when you want the assistant to inspect a place butnot change it. It's enforced in two independent places — either one blocks amutation:
- Server (launch-time lock). Start the server with
ROBLOX_STUDIO_MCP_READONLY=1. Mutating tools are refused before the requestever reaches Studio, and the server logsREAD-ONLY modeon startup. - Plugin (runtime toggle). Click MCP ▸ Read-only in Studio. While active,the plugin refuses mutating commands no matter what the client sends. Readsstill work. This is authoritative — it's enforced at the point of execution,under the control of whoever is at the Studio session.
Note:
run_luauis a read tool for gating purposes, but it can executearbitrary Luau — including code that mutates the DataModel. Read-only mode doesnot sandboxrun_luau; use the plugin toggle plus your own review if youneed a hard guarantee against changes.
Security notes
- The bridge binds to
127.0.0.1only. run_luauexecutes arbitrary Luau in the Studio/plugin context. Onlyconnect 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 youwant look-but-don't-touch access.
Troubleshooting
- "No Roblox Studio plugin is connected." The plugin does not auto-connectwhen Studio starts — click MCP ▸ Connect on the toolbar after opening yourplace. The button is a toggle; make sure it's active.
- Edited the plugin but nothing changed? Studio doesn't hot-reload localplugins. Fully restart Studio, then click Connect again.
- HTTP requests blocked. Enable Game Settings ▸ Security ▸ Allow HTTPRequests and reconnect (the plugin also tries to set this for you).
- Deletes should be undoable.
delete_instancere-parents tonilinside aChangeHistoryServicerecording, 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 theMCP ▸ Read-only toggle is active in Studio, or the server was started with
ROBLOX_STUDIO_MCP_READONLYset. See Read-only mode.
Roadmap
Done
- Verified in real Studio
- Selection-aware tools (operate on the current Studio selection)
- Output/
printcapture forrun_luau - Batch/transaction commands
- Optional read-only mode
Planned
- Rojo-friendly project layout for the plugin
Contributing
Issues and PRs welcome. Run npm run build before pushing; CI builds on Node 20.
License
MIT © Emir Coban