roblox-mcp-server
v3.1.3
Published
An MCP server for interacting with Roblox Game Clients (Delta, etc) over WebSocket+HTTP, with a server side decompile pipeline and in game UI.
Maintainers
Readme
pick your MCP client
| client | guide | |-------------------------------------------|--------------------------------------| | Claude Desktop + Claude Code | CLAUDE_MCP.md | | Cursor | CURSOR_MCP.md | | Windsurf | WINDSURF_MCP.md | | VS Code (Copilot Chat) | VSCODE_MCP.md | | Zed | ZED_MCP.md | | Cline | CLINE_MCP.md | | Roo Code | ROOCODE_MCP.md | | Continue | CONTINUE_MCP.md |
each file has the exact config path, JSON shape, and copy paste snippet for that client.
what it does
bridges any MCP capable client (Claude Code, Cursor, Windsurf, etc) to a Roblox executor running in a real game session. lets the model decompile scripts, spy remotes, click buttons, type text, fire signals, run lua, all from chat.
upstream by notpoiu. this fork rebuilds the decompile path end to end and adds a real UI.
new in v3.1.2
token-safe MCP responses
every tool call used to dump pretty-printed Lua with a { "value", n = 1 } envelope. on a 200k context that adds up fast. v3.1.2:
- connector serializes compact, no
Prettify, non = 1array tail for single-value returns - server-side
tokensafe()post-filter strips legacy envelopes from older connectors and hard-caps each response at 12000 chars with a clean truncation footer list-clientsJSON dropped from pretty to compact (4-5x smaller payload)- default limits trimmed:
script-greplimit50 → 20,maxMatchesPerScript20 → 5,contextLines2 → 1dump-visible-uimaxItems500 → 150get-console-output,search-instances,get-remote-spy-logslimit 50 → 25get-descendants-treemaxChildren50 → 20
bump the per-tool limit explicitly when you actually need the breadth, otherwise the new defaults already cover 90% of asks for a fraction of the tokens.
device aware throttling (anti crash/lag)
connector now detects mobile / console / desktop on boot and tunes everything heavy.
| profile | upload batch | scan chunk | scan sleep | spy interval | |----------|--------------|------------|------------|--------------| | mobile | 3 | 80 | 60ms | 600ms | | console | 4 | 120 | 40ms | 400ms | | desktop | 8 | 250 | 10ms | 200ms |
decomp mapping, gc scans, spy polling all read from DeviceProfile. mobile and Xbox stop choking under the desktop tuned defaults.
push events instead of polling
connector ↔ server bridge now carries outbound event messages. server fans them out over an SSE stream at /api/events (filter with ?kind= and ?clientId=).
new MCP tool subscribe-remote flips a diff poller inside the connector. every new Cobalt remote call gets pushed as a remote-call event. agents stop hammering get-remote-spy-logs in a loop.
hot reload
new MCP tool hot-reload ships a Luau chunk to the active client. state survives across reloads via getgenv().STATE. takes either inline source or a server local filePath.
-- inside your live script
getgenv().STATE.counter = (getgenv().STATE.counter or 0) + 1
print("reloaded", getgenv().STATE.counter)new in v3.0.0
decompile pipeline that doesn't fail
old chain (lua.expert then medal then konstant) ran client side, fell over on big scripts. PlayerModule decompile would 50x after 112 seconds.
new pipeline lives on the server:
client sends bytecode
↓
[1] SHA384 cache hit instant return (every decompile cached forever)
↓
[2] CoreScript path? pull pristine source from MaximumADHD/Roblox-Client-Tracker
↓
[3] race lua.expert + medal + konstant in parallel first valid wins
↓
[4] fall back to native executor decompilePlayerModule and all CoreScripts now return clean Roblox source in under a second.
new HTTP api
| route | what it does |
|----------------------------------|-----------------------------------------------------------------|
| POST /api/decompile | the orchestrator above. takes bytecode, returns source |
| POST /api/decompile/disasm | full bytecode disasm with LOP_NATIVECALL highlighting |
| POST /api/decompile/constants | extract every string / number / import without decompiling |
| POST /api/decompile/diff | per proto hash diff between two bytecode versions |
| GET /api/decompile/stats | cache hit rate, provider wins, entry count |
| GET /api/version | server + connector version, platform aware update hints |
| GET /script.luau | served connector (always in sync with running server) |
| GET /ui.luau | in game settings + connection UI |
in game UI
new ui.luau ships a glassy connection panel. drop it in the executor:
loadstring(game:HttpGet("https://gitlab.com/DexCodeSX/roblox-executor-mcp/-/raw/main/ui.luau"))()features:
- bridge URL input with paste / clear / autosave
- version pill that turns green / yellow / red based on
/api/versioncompare - platform aware "how to update" hint (macos / linux / termux / wsl / windows)
- live activity log of the handshake
- settings drawer with three toggles (websocket / lazy decomp / autoconnect)
- drag works on mouse + touch (PC + mobile)
- animated background orbs that drift behind the window
- saved URL auto checks on open
version sync
connector reports its version + platform to /api/version. server replies with one of:
| status | what happens |
|------------|-------------------------------------------------------------------|
| current | green pill, boots normally |
| outdated | yellow banner with platform specific update command, boots anyway |
| blocked | red banner, boot refused, you must update first |
MIN_BOOTABLE_CONNECTOR in src/http/routes/api/version.ts gates blocked vs outdated. bump it when the server adds a contract the old connector can't speak.
install
via npm (recommended):
npm install -g roblox-mcp-server
# postinstall prints the absolute boot path, something like:
# /usr/lib/node_modules/roblox-mcp-server/dist/index.js
# point your MCP client at that path, or use the `roblox-mcp` bin directlyfrom source:
git clone https://gitlab.com/DexCodeSX/roblox-executor-mcp
cd roblox-executor-mcp
npm install
npm run build
npm startserver listens on :16384. dashboard at http://localhost:16384/.
mobile note
if you're running the server on the same Android phone (Termux) and connecting from Roblox on the same phone, localhost:16384 will NOT work. Roblox runs in its own network sandbox and can't see Termux. you MUST tunnel:
cloudflared tunnel --url http://localhost:16384paste the https://*.trycloudflare.com URL into the in game UI. on Windows / macOS / Linux desktop, localhost works fine because the Roblox process and the server share the same OS network namespace.
the UI will refuse to connect to localhost from a mobile device and tell you to start a tunnel.
register with your MCP client
pick one from the clients table above. each file has the exact JSON / YAML for that client.
quick auto installer (Claude Code, Cursor, others where the CLI supports it):
npm run install:harnesseswrites the right config and prints the loader.
usage in roblox
simplest path, recommended:
loadstring(game:HttpGet("https://gitlab.com/DexCodeSX/roblox-executor-mcp/-/raw/main/ui.luau"))()skip the UI, boot the connector directly:
getgenv().BridgeURL = "your-bridge-url"
loadstring(game:HttpGet("https://gitlab.com/DexCodeSX/roblox-executor-mcp/-/raw/main/connector.luau"))()options:
getgenv().BridgeURL = "localhost:16384"
getgenv().DisableWebSocket = true -- http polling, steadier on mobile
getgenv().DisableInitialScriptDecompMapping = true -- lazy: start mapping on first /grep callarchitecture
src/
decompile/
cache.ts SHA384 disk cache at ~/.roblox-mcp/decompile-cache/
github-mirror.ts maps CoreScript paths to MaximumADHD/Roblox-Client-Tracker
providers.ts lua.expert + medal + konstant racers
orchestrator.ts ties it all together
luau-bytecode.ts pure TS Luau bytecode reader (v3 through v6)
http/
routes/
api/decompile.ts POST orchestrator
api/decompile/disasm.ts POST bytecode disasm + LOP_NATIVECALL scan
api/decompile/constants.ts POST string/number/import extract
api/decompile/diff.ts POST per proto hash diff
api/decompile/stats.ts GET cache stats
api/version.ts GET version + platform compare
script.luau.ts GET connector
ui.luau.ts GET in game UI
tools/ MCP tools (script-grep, get-script-content, etc)
bridge/ connector handshake, websocket fallbackcache survives restarts. entries are content addressed by bytecode SHA384, so the same decompile is never paid for twice across any client, any place, any session.
bytecode disassembler example
curl -X POST http://localhost:16384/api/decompile/disasm \
-H 'Content-Type: application/json' \
-d '{"bytecodeBase64":"BAUDfgEHQF..."}'returns:
-- Bytecode v6, 142 strings, 17 protos
-- proto[0] init (params=0 ups=0 vararg=true stack=12 children=3)
0: GETIMPORT A=0 B=1 C=0 ; game.GetService
1: NAMECALL A=0 B=0 C=0 AUX=... ; "GetService"plus a nativeCalls array pointing at every LOP_NATIVECALL site. those skip the Lua VM and your hookmetamethod hooks won't see them mid execution. useful for AC analysis.
credits
- upstream: notpoiu/roblox-executor-mcp
- CoreScript mirror: MaximumADHD/Roblox-Client-Tracker
- decompile providers: lua.expert, medal.upio.dev, Konstant
- Luau bytecode spec: luau-lang/luau
not affiliated with Roblox Corporation. for research and learning. use responsibly.
