roblox-studio-mcp
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.
README
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.4 — 24 tools, verified in real Studio. 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) │
└────────────┘ └──────────────────┘ └──────────────┘
- 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 |
|---|---|
get_instances |
List the connected Studio windows (for multi-window routing). |
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 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 open →
instance_idis 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_idgiven → matched against each window'sinstanceIdor its rawsessionId.
Call get_instances to see what's connected — each entry has an
instanceId (session-scoped, e.g. place:<placeId> or untitled:<id>),
placeName, placeId, pluginVersion, and how long ago it last polled. A
window that stops polling is dropped after ~30s.
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.rbxmthere. - 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 runrojo servewith the Rojo Studio plugin for live sync during development. Studio does not hot-reload an installed plugin, so after rebuilding the.rbxmyou 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 = truefor 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_scriptreturnsnumberedSource(1: local x = …),lineCount, andsourceLength. PassstartLine/endLineto read a slice; without a range a large script is truncated to the first 400 lines with anoteon how to page.grep_scriptsfinds where something lives —{ path, line, text }matches across every script under a path (literal by default;plain=falsefor a Luau pattern).edit_script_lines/insert_script_lines/delete_script_lineschange a line range, insert before a line (lineCount+1appends), 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_docsreturns an engine reference page as markdown (fromcreate.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_infolists a class's properties (with value types), methods, events, and callbacks from the official API dump, walking the full inheritance chain — soget_class_info PartincludesAnchored(fromBasePart), not justPart'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 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. 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_luauis a read tool for gating purposes, but it can execute arbitrary Luau — including code that mutates the DataModel. Read-only mode does not sandboxrun_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.1only. -
Origin/Host guard (always on). The bridge rejects any request carrying an
Originheader or a non-loopbackHost— so a web page in your browser can't drive Studio viafetch()(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_luauexecutes 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
.rbxmfromplugin/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_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 the
MCP ▸ Read-only toggle is active in Studio, or the server was started with
ROBLOX_STUDIO_MCP_READONLYset. 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
MCPTokensetting is missing or wrong — set it to matchROBLOX_STUDIO_MCP_TOKEN(see Security).
Roadmap
Done
- [x] Verified in real Studio
- [x] Selection-aware tools (operate on the current Studio selection)
- [x] Output/
printcapture forrun_luau - [x] Batch/transaction commands
- [x] Optional read-only mode
- [x] Rojo-friendly project layout for the plugin
Planned
Nothing queued right now — ideas and issues welcome!
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
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。