roblox-studio-mcp

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.

Category
访问服务器

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)      │
└────────────┘                 └──────────────────┘                    └──────────────┘
  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.

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 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 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.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).

Roadmap

Done

  • [x] Verified in real Studio
  • [x] Selection-aware tools (operate on the current Studio selection)
  • [x] Output/print capture for run_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

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选