@el4cteo/rbx-studio-mcp 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,54 +1,35 @@
1
1
  # Roblox Studio MCP
2
2
 
3
- Let an AI agent drive Roblox Studio: read your place, edit scripts, build and generate geometry, run playtests, take screenshots. 30 tools. MIT.
3
+ Let an AI agent drive Roblox Studio: read your place, edit scripts, build geometry, run playtests, take screenshots. 31 tools. MIT.
4
4
 
5
- ![The Studio MCP panel: a live activity band, a call log with latencies, and the theme drawer open](docs/rbx-studio.png)
5
+ ![The Studio MCP panel](docs/rbx-studio.png)
6
6
 
7
7
  ## Install
8
8
 
9
- **1. The Studio plugin**
9
+ **1. The plugin**
10
10
 
11
11
  ```bash
12
12
  npx -y @el4cteo/rbx-studio-mcp --install-plugin
13
13
  ```
14
14
 
15
- Or download `StudioMCP.rbxmx` from [Releases](https://github.com/EL4CTEO/rbx-studio-mcp/releases) into your Studio plugins folder.
15
+ Or drop `StudioMCP.rbxmx` from [Releases](https://github.com/EL4CTEO/rbx-studio-mcp/releases) into your Studio plugins folder.
16
16
 
17
- **2. The server**, in whichever client you use:
18
-
19
- <details>
20
- <summary><b>Claude Code</b></summary>
17
+ **2. The server**
21
18
 
22
19
  ```bash
23
20
  claude mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
24
21
  ```
25
- </details>
26
22
 
27
23
  <details>
28
- <summary><b>Codex CLI</b></summary>
24
+ <summary>Other clients</summary>
25
+
26
+ Codex CLI:
29
27
 
30
28
  ```bash
31
29
  codex mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
32
30
  ```
33
- </details>
34
-
35
- <details>
36
- <summary><b>Cursor</b> — <code>~/.cursor/mcp.json</code> or <code>.cursor/mcp.json</code></summary>
37
-
38
- ```json
39
- {
40
- "mcpServers": {
41
- "roblox-studio": {
42
- "command": "npx",
43
- "args": ["-y", "@el4cteo/rbx-studio-mcp"]
44
- }
45
- }
46
- }
47
- ```
48
- </details>
49
31
 
50
- <details>
51
- <summary><b>Claude Desktop</b> — <code>claude_desktop_config.json</code></summary>
32
+ Cursor, Claude Desktop, Gemini CLI, Windsurf — add to their config file:
52
33
 
53
34
  ```json
54
35
  {
@@ -60,60 +41,13 @@ codex mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
60
41
  }
61
42
  }
62
43
  ```
63
- </details>
64
-
65
- <details>
66
- <summary><b>Gemini CLI</b> — <code>~/.gemini/settings.json</code></summary>
67
44
 
68
- ```json
69
- {
70
- "mcpServers": {
71
- "roblox-studio": {
72
- "command": "npx",
73
- "args": ["-y", "@el4cteo/rbx-studio-mcp"]
74
- }
75
- }
76
- }
77
- ```
78
- </details>
45
+ VS Code / Copilot (`.vscode/mcp.json`) uses `"servers"` instead of `"mcpServers"`, plus `"type": "stdio"`.
79
46
 
80
- <details>
81
- <summary><b>Windsurf</b> — <code>~/.codeium/windsurf/mcp_config.json</code></summary>
47
+ opencode (`opencode.json`):
82
48
 
83
49
  ```json
84
50
  {
85
- "mcpServers": {
86
- "roblox-studio": {
87
- "command": "npx",
88
- "args": ["-y", "@el4cteo/rbx-studio-mcp"]
89
- }
90
- }
91
- }
92
- ```
93
- </details>
94
-
95
- <details>
96
- <summary><b>VS Code / Copilot</b> — <code>.vscode/mcp.json</code></summary>
97
-
98
- ```json
99
- {
100
- "servers": {
101
- "roblox-studio": {
102
- "type": "stdio",
103
- "command": "npx",
104
- "args": ["-y", "@el4cteo/rbx-studio-mcp"]
105
- }
106
- }
107
- }
108
- ```
109
- </details>
110
-
111
- <details>
112
- <summary><b>opencode</b> — <code>opencode.json</code></summary>
113
-
114
- ```json
115
- {
116
- "$schema": "https://opencode.ai/config.json",
117
51
  "mcp": {
118
52
  "roblox-studio": {
119
53
  "type": "local",
@@ -125,25 +59,11 @@ codex mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
125
59
  ```
126
60
  </details>
127
61
 
128
- **3.** Open Studio and accept the `127.0.0.1` prompt — the plugin connects automatically. Verify with `studio_status`.
129
-
130
- **4.** `debug` additionally needs **Debugger Luau API** in File → Beta Features, plus a Studio restart. Nothing else requires it.
131
-
132
- ![The Debugger Luau API beta feature toggle in Studio](docs/betatoggle.png)
62
+ **3.** Open Studio and accept the `127.0.0.1` prompt. Check it works with `studio_status`.
133
63
 
134
- Port defaults to **44755** — change with `--port` or `ROBLOX_STUDIO_MCP_PORT`, and match it in the plugin widget. Loopback only.
64
+ Something wrong? Run `npx -y @el4cteo/rbx-studio-mcp doctor` — it says what is broken and how to fix it.
135
65
 
136
- If anything looks wrong, run the health check. It reports Node, the plugin install, the bridge, and every connected Studio, and ends each line with the fix:
137
-
138
- ```bash
139
- npx -y @el4cteo/rbx-studio-mcp doctor
140
- ```
141
-
142
- ## Multiple agents
143
-
144
- Register the server in as many clients as you like. No extra configuration.
145
-
146
- Each agent keeps its own target, so two agents can work on two open places. Subagents share their parent's target — give them an explicit `studioId` per call.
66
+ Port is **44755**, loopback only. Change it with `--port` and match it in the plugin.
147
67
 
148
68
  ## Tools
149
69
 
@@ -153,101 +73,67 @@ Each agent keeps its own target, so two agents can work on two open places. Suba
153
73
  | **Discover** | `tree` `inspect` `find` `api` |
154
74
  | **Scripts** | `script_read` `script_edit` `script_grep` `script_create` |
155
75
  | **Instances** | `create` `modify` `delete` `move` |
156
- | **World** | `geometry` `generate` `assets` `collision` `undo` |
76
+ | **World** | `geometry` `terrain` `generate` `assets` `collision` `undo` |
157
77
  | **Run & debug** | `playtest` `execute_luau` `character` `input` `console` `debug` `performance` |
158
78
  | **Look** | `screenshot` `viewport` `device` |
159
79
 
160
- `geometry` holds every mesh operation: `union`, `subtract`, `intersect`, `fragment`, `segment` (cut a mesh into parts you name) and `sweep` (the volume a part moves through, and what it would hit).
161
-
162
- `generate` builds 3D models from a prompt using Roblox's Cube model — `Body1` for props, `Car5` for a body and four named wheels a script can drive, or your own part names.
80
+ Write tools take arrays — ten script edits is one call, one **Ctrl+Z**, and all-or-nothing.
163
81
 
164
- Gotchas: during a playtest two sessions connect — pass `studioId` explicitly and use the edit session for anything that must persist. `device` emulation persists until `device op="stop"`. Generated meshes are edit-mode only; their content reads as empty inside a playtest.
82
+ Two things to watch: a playtest connects a second session, so pass `studioId` and use the edit one for changes that must last; `device` emulation stays on until `device op="stop"`.
165
83
 
166
84
  ## The console panel
167
85
 
168
- Every call is logged with its latency, above a trace of the last forty. The badge names which agents are connected, with process id and uptime.
169
-
170
- Under the header is a command line. Type a command, or type a sentence and it goes to a coding agent.
86
+ Every call is logged with how long it took. Below the header is a command line — type a command, or type a sentence and a coding agent answers it.
171
87
 
172
88
  | | |
173
89
  |---|---|
174
90
  | `help` | list everything |
175
- | `doctor` | check every part of the setup |
91
+ | `doctor` | check the setup |
176
92
  | `status` `version` `place` `clients` | what this session is |
177
- | `studios` `use <n>` | which Studio window calls land on |
178
- | `theme [name]` `visuals` `log [level]` `clear` `copy` | the panel itself |
93
+ | `studios` `use <n>` | which Studio window calls go to |
94
+ | `theme [name]` `visuals` `log [level]` `clear` `copy` | the panel |
179
95
  | `port [n]` `reconnect` | the connection |
180
- | `agent [use <id>\|new]` `stop` | which coding agent runs your prompts |
96
+ | `agent [use <id>\|new]` `stop` | which agent runs your prompts |
181
97
  | anything else | sent to that agent |
182
98
 
183
- Up and down walk the history, Tab completes.
99
+ Arrows walk the history, Tab completes.
184
100
 
185
- **Prompts start a real agent.** The bridge runs whichever tool you have on PATH — Claude Code, Codex, opencode, DeepSeek Harness, Gemini, Cursor, Amp, Qwen Code, Factory Droid, goose, GitHub Copilot CLI, Aider, Crush — in headless mode. It connects back to this same bridge, drives the same Studio, and its work streams into the log line by line. It is a separate session from the terminal you may already have open, billed separately, and it is allowed the `rbx-studio` tools and nothing else. `stop` cancels it.
101
+ **Prompts start a real agent** — whichever you have on PATH: Claude Code, Codex, opencode, Gemini, Cursor, Amp, Qwen Code, Factory Droid, goose, Copilot CLI, Aider, Crush, DeepSeek Harness. It runs headless, drives the same Studio, and its work appears in the log. It is a separate session from your terminal, billed separately, and allowed the `rbx-studio` tools only. `stop` cancels it.
186
102
 
187
- Hover the tab on the right edge for eight themes — Lattice, Observatory, Orbit, Void, Nebula, Aurora, Phosphor, Blueprint. Each redraws the activity band, not just its colours. Your pick is remembered.
103
+ Eight themes behind the tab on the right edge. Your pick is remembered.
104
+
105
+ ## Why this one
106
+
107
+ - **Push, not poll** — 13.6 ms per call against 25.8 ms.
108
+ - **Safe script edits** — writes go through the script editor, so unsaved work survives.
109
+ - **Stale edits are refused** — pass back the `rev` from `script_read` and a write lands only if nobody else touched the file.
110
+ - **Property names are checked** against the running engine, so `Anchorred` comes back as a suggestion, not a runtime error.
188
111
 
189
112
  ## DeepSeek Harness (dsh)
190
113
 
191
- dsh is "everything is a plugin", and this server becomes one through its MCP bridge. One overlay row, and the tools arrive as `mcp__rbx-studio__*` — the same names Claude Code and Codex use.
114
+ This server registers as a dsh plugin. `config/dsh.cordis.yml` is the row:
192
115
 
193
116
  ```sh
194
117
  dsh --profile headless --patch config/dsh.cordis.yml "add a spawn point"
195
118
  ```
196
119
 
197
- To keep it, append that row to your own patch layer — `$DSH_HOME/profiles/<name>/cordis.patch.yml` for one profile, `$DSH_HOME/cordis.patch.yml` for all of them. Don't overwrite the file; it may already hold unrelated patches.
198
-
199
- `config/dsh.cordis.yml` in this repo is the row. The console panel also lists `dsh` as an agent, and passes the overlay itself, so a prompt typed there works before you have merged anything.
200
-
201
- Two limits, both dsh's rather than ours: its headless profile prints the final answer instead of streaming, so the panel shows the reply at the end rather than step by step, and it has no `--resume`, so every prompt is a fresh conversation. It needs `DEEPSEEK_API_KEY`.
202
-
203
- ## Batching
204
-
205
- Every write tool takes an array — ten script edits is one call.
206
-
207
- | tool | takes | cap |
208
- |---|---|---|
209
- | `create` | instances, each nesting `children` to any depth | 100 |
210
- | `modify` | entries, each with an unlimited list of `paths` | 100 entries |
211
- | `delete` | paths | 200 |
212
- | `move` | moves | 200 |
213
- | `script_edit` | edits, across any number of scripts | 50 |
214
- | `script_create` | scripts | 50 |
215
- | `inspect` | paths | 50 |
216
- | `input` | input steps, delivered in order | 40 |
217
-
218
- `modify` caps *entries*, not targets — one entry can anchor five hundred parts.
219
-
220
- Each batch is one **Ctrl+Z**, and all-or-nothing: a failed match leaves the place untouched.
221
-
222
- ## Why this one
223
-
224
- - **Push, not poll.** 50 sequential round trips average 13.6 ms, against 25.8 ms polling — reproduce with `node scripts/latency.mjs --count 50 --compare`.
225
- - **Safe script edits.** Writes go through `ScriptEditorService:UpdateSourceAsync`, so your unsaved editor buffer survives.
226
- - **Edits can refuse to be stale.** `script_read` prints a `rev`; pass it back and the write is rejected if anyone changed the script meanwhile, instead of landing on lines that moved.
227
- - **One Ctrl+Z per call.** Every batch is a single undo recording.
228
- - **Property names are checked** against the running engine's API dump, so a typo comes back as `Anchorred` → `Anchored` instead of a runtime error.
229
- - **~16k tokens of schema**, cursor-paged and capped, with `detail: concise | standard | full`.
230
-
231
- Not built here: material generation.
120
+ To keep it, append that row to your own `cordis.patch.yml`. Needs `DEEPSEEK_API_KEY`.
232
121
 
233
122
  ## Security
234
123
 
235
- Loopback only. Requires a header a browser cannot set cross-origin, which closes the DNS-rebinding hole. Your experience's "Allow HTTP Requests" setting is untouched.
124
+ Loopback only, and requires a header a browser cannot set cross-origin. Your experience's "Allow HTTP Requests" setting is untouched.
236
125
 
237
126
  ## Development
238
127
 
239
128
  ```bash
240
129
  npm install
241
130
  npm run build # TypeScript -> dist/
242
- npm run build:plugin # plugin/src -> build/StudioMCP.rbxmx
243
- npm run install:plugin # build + copy into the Studio plugins folder
244
- npm test # plugin (Luau) + bridge (Node) tests
131
+ npm run install:plugin # build the plugin and copy it into Studio
132
+ npm test
245
133
  ```
246
134
 
247
135
  Needs `luau`, `luau-compile` and `luau-analyze` from [the Luau releases](https://github.com/luau-lang/luau/releases) on `PATH` or in `tools/`.
248
136
 
249
- `evals/` holds ten questions answerable only by driving a real Studio session — see [evals/README.md](evals/README.md).
250
-
251
137
  ## Licence
252
138
 
253
139
  MIT.
@@ -121,9 +121,23 @@ export class RemoteBridge {
121
121
  };
122
122
  }
123
123
  async post(path, body, timeoutMs) {
124
- let response;
124
+ //[[ The body read is inside the try, and that is the whole point of it.
125
+ //
126
+ // Only the fetch used to be guarded. But the owner does not always die
127
+ // before answering -- it dies while answering, which is exactly what a
128
+ // handover looks like: headers arrive, the process exits, and the socket
129
+ // closes mid-body. `response.json()` then throws its own raw
130
+ // "TypeError: fetch failed / SocketError: other side closed", outside the
131
+ // catch, and the user is handed a Node network error where OWNER_GONE
132
+ // belongs -- with none of the "try again in a few seconds" that makes it
133
+ // actionable.
134
+ //
135
+ // Reported as an error appearing right below the takeover notice, which is
136
+ // the one moment this is guaranteed to happen.
137
+ //]]
138
+ let payload;
125
139
  try {
126
- response = await fetch(`${this.base}${path}`, {
140
+ const response = await fetch(`${this.base}${path}`, {
127
141
  method: "POST",
128
142
  headers: this.headers,
129
143
  body: JSON.stringify(body),
@@ -131,11 +145,11 @@ export class RemoteBridge {
131
145
  // its diagnosis reaches the caller, rather than being cut off by ours.
132
146
  signal: AbortSignal.timeout(timeoutMs + 10_000),
133
147
  });
148
+ payload = (await response.json());
134
149
  }
135
150
  catch (cause) {
136
151
  throw this.unreachable(cause);
137
152
  }
138
- const payload = (await response.json());
139
153
  if (!payload.ok) {
140
154
  const error = payload.error ?? { code: "PEER_ERROR", message: "the bridge owner refused" };
141
155
  throw new ToolError(error.code, error.message);
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/bridge/remote.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/bridge/remote.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@el4cteo/rbx-studio-mcp",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "MCP server for Roblox Studio. 31 tools, push-based SSE bridge, editor-safe script edits, one-step undo.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,7 +9,7 @@
9
9
 
10
10
  local Config = {}
11
11
 
12
- Config.PLUGIN_VERSION = "0.5.3"
12
+ Config.PLUGIN_VERSION = "0.5.5"
13
13
 
14
14
  -- Fingerprint of plugin/src, stamped in by scripts/build-plugin.mjs. The server
15
15
  -- computes the same hash from its own copy of the sources and compares, so a
@@ -180,4 +180,55 @@ function handshake(port, studioId) {
180
180
  await owner.close();
181
181
  }
182
182
 
183
+ // An owner that dies WHILE answering is still OWNER_GONE, not a raw Node error.
184
+ //
185
+ // The shipped bug: only the fetch was guarded, so a socket cut after the
186
+ // headers threw "TypeError: fetch failed / SocketError: other side closed"
187
+ // straight past the catch. It surfaced right under the takeover notice, which
188
+ // is the exact moment a handover cuts a live response.
189
+ //
190
+ // The fake owner declares a Content-Length it never delivers, then hangs up.
191
+ // That distinction is the whole test: a socket destroyed BEFORE the headers
192
+ // makes Node reject the fetch itself, which the old code already caught -- the
193
+ // bug only shows when the reply has started and stops halfway. Two earlier
194
+ // versions of this test cut the socket too early, passed with the bug present,
195
+ // and proved nothing.
196
+ {
197
+ const halfDead = createServer((req, res) => {
198
+ if (req.url === "/identity") {
199
+ res.writeHead(200, { "Content-Type": "application/json" });
200
+ res.end(JSON.stringify({ server: "roblox-studio-mcp", protocolVersion: 1, pid: 1 }));
201
+ return;
202
+ }
203
+ res.writeHead(200, { "Content-Type": "application/json", "Content-Length": "5000" });
204
+ res.write('{"ok":true,"data":"' + "x".repeat(100));
205
+ setTimeout(() => res.socket.destroy(), 150);
206
+ });
207
+ await new Promise((resolve) => halfDead.listen(PORT, "127.0.0.1", resolve));
208
+
209
+ const peer = await startBridgeServer({ port: PORT });
210
+ assert.equal(peer.owner, false, "the peer proxies to whatever holds the port");
211
+
212
+ let raised;
213
+ try {
214
+ await peer.bridge.call("studio.status", {});
215
+ } catch (cause) {
216
+ raised = cause;
217
+ }
218
+
219
+ assert.ok(raised, "a truncated answer must not resolve as success");
220
+ assert.equal(
221
+ raised.code,
222
+ "OWNER_GONE",
223
+ `a body cut short is the owner going away, not a raw ${raised?.name}`,
224
+ );
225
+ assert.ok(
226
+ /try the call again/.test(raised.hint ?? ""),
227
+ "and says what to do about it, which a raw socket error never does",
228
+ );
229
+
230
+ await peer.close();
231
+ await new Promise((resolve) => halfDead.close(resolve));
232
+ }
233
+
183
234
  process.stdout.write("failover: ok\n");