@el4cteo/rbx-studio-mcp 0.2.0 → 0.2.8
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 +217 -217
- package/dist/bridge/api.js +29 -0
- package/dist/bridge/api.js.map +1 -1
- package/dist/bridge/remote.js +35 -0
- package/dist/bridge/remote.js.map +1 -1
- package/dist/bridge/rpc.js +81 -2
- package/dist/bridge/rpc.js.map +1 -1
- package/dist/bridge/server.js +56 -3
- package/dist/bridge/server.js.map +1 -1
- package/dist/index.js +86 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/input.js +63 -1
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/perf.js +73 -16
- package/dist/tools/perf.js.map +1 -1
- package/dist/tools/session.js +25 -4
- package/dist/tools/session.js.map +1 -1
- package/package.json +8 -3
- package/plugin/src/Config.luau +1 -1
- package/plugin/src/Console.luau +204 -4
- package/plugin/src/Transport.luau +78 -19
- package/plugin/src/Visuals.luau +217 -20
- package/plugin/src/handlers/Input.luau +41 -1
- package/plugin/src/handlers/Perf.luau +662 -645
- package/plugin/src/init.server.luau +48 -11
- package/scripts/check-plugin.mjs +29 -1
- package/scripts/test-bridge.mjs +103 -0
- package/scripts/test-transport.mjs +58 -0
package/README.md
CHANGED
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
# Roblox Studio MCP
|
|
2
|
-
|
|
3
|
-
MCP server for Roblox Studio: 29 tools over a push-based bridge, batched writes that undo as one step, editor-safe script edits. MIT.
|
|
4
|
-
|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
## Install
|
|
8
|
-
|
|
9
|
-
**1. The Studio plugin**
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npx -y @el4cteo/rbx-studio-mcp --install-plugin
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Or download `StudioMCP.rbxmx` from [Releases](https://github.com/EL4CTEO/rbx-studio-mcp/releases) into your Studio plugins folder.
|
|
16
|
-
|
|
17
|
-
**2. The server**, in whichever client you use:
|
|
18
|
-
|
|
19
|
-
<details>
|
|
20
|
-
<summary><b>Claude Code</b></summary>
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
claude mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
|
|
24
|
-
```
|
|
25
|
-
</details>
|
|
26
|
-
|
|
27
|
-
<details>
|
|
28
|
-
<summary><b>Codex CLI</b></summary>
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
codex mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
|
|
32
|
-
```
|
|
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
|
-
|
|
50
|
-
<details>
|
|
51
|
-
<summary><b>Claude Desktop</b> — <code>claude_desktop_config.json</code></summary>
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"mcpServers": {
|
|
56
|
-
"roblox-studio": {
|
|
57
|
-
"command": "npx",
|
|
58
|
-
"args": ["-y", "@el4cteo/rbx-studio-mcp"]
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
</details>
|
|
64
|
-
|
|
65
|
-
<details>
|
|
66
|
-
<summary><b>Gemini CLI</b> — <code>~/.gemini/settings.json</code></summary>
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
{
|
|
70
|
-
"mcpServers": {
|
|
71
|
-
"roblox-studio": {
|
|
72
|
-
"command": "npx",
|
|
73
|
-
"args": ["-y", "@el4cteo/rbx-studio-mcp"]
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
</details>
|
|
79
|
-
|
|
80
|
-
<details>
|
|
81
|
-
<summary><b>Windsurf</b> — <code>~/.codeium/windsurf/mcp_config.json</code></summary>
|
|
82
|
-
|
|
83
|
-
```json
|
|
84
|
-
{
|
|
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
|
-
"mcp": {
|
|
118
|
-
"roblox-studio": {
|
|
119
|
-
"type": "local",
|
|
120
|
-
"command": ["npx", "-y", "@el4cteo/rbx-studio-mcp"],
|
|
121
|
-
"enabled": true
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
</details>
|
|
127
|
-
|
|
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
|
-

|
|
133
|
-
|
|
134
|
-
Port defaults to **44755** — change with `--port` or `ROBLOX_STUDIO_MCP_PORT`, and match it in the plugin widget. Loopback only.
|
|
135
|
-
|
|
136
|
-
## Multiple agents
|
|
137
|
-
|
|
138
|
-
Register the server in as many clients as you like — the plugin connects out to one port, the first server owns it and the rest proxy through. No configuration, no second Studio connection.
|
|
139
|
-
|
|
140
|
-
Each agent keeps its own target (`set_active_studio` is per client), so two agents can work on two open places and neither can retarget the other. Pass `studioId` on a single call to reach elsewhere without changing your default.
|
|
141
|
-
|
|
142
|
-
Subagents share their parent's connection and therefore its target — a subagent calling `set_active_studio` silently retargets its parent. Give subagents an explicit `studioId` per call.
|
|
143
|
-
|
|
144
|
-
## Tools
|
|
145
|
-
|
|
146
|
-
| | |
|
|
147
|
-
|---|---|
|
|
148
|
-
| **Session** | `studio_status` `list_studios` `set_active_studio` |
|
|
149
|
-
| **Discover** | `tree` `inspect` `find` `api` |
|
|
150
|
-
| **Scripts** | `script_read` `script_edit` `script_grep` `script_create` |
|
|
151
|
-
| **Instances** | `create` `modify` `delete` `move` |
|
|
152
|
-
| **World** | `geometry` `assets` `collision` `undo` |
|
|
153
|
-
| **Run & debug** | `playtest` `execute_luau` `character` `input` `console` `debug` `performance` |
|
|
154
|
-
| **Look** | `screenshot` `viewport` `device` |
|
|
155
|
-
|
|
156
|
-
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"`.
|
|
157
|
-
|
|
158
|
-
## Batching
|
|
159
|
-
|
|
160
|
-
Every write tool takes an array — ten script edits or two hundred deletions is one call.
|
|
161
|
-
|
|
162
|
-
| tool | takes | cap |
|
|
163
|
-
|---|---|---|
|
|
164
|
-
| `create` | instances, each nesting `children` to any depth | 100 |
|
|
165
|
-
| `modify` | entries, each with an unlimited list of `paths` | 100 entries |
|
|
166
|
-
| `delete` | paths | 200 |
|
|
167
|
-
| `move` | moves | 200 |
|
|
168
|
-
| `script_edit` | edits, across any number of scripts | 50 |
|
|
169
|
-
| `script_create` | scripts | 50 |
|
|
170
|
-
| `inspect` | paths | 50 |
|
|
171
|
-
| `input` | input steps, delivered in order | 40 |
|
|
172
|
-
|
|
173
|
-
`modify` caps *entries*, not targets — one entry can anchor five hundred parts, so pair it with `find` to change a whole place in one call.
|
|
174
|
-
|
|
175
|
-
Each batch is a single `ChangeHistoryService` recording: **one Ctrl+Z**. Batches are all-or-nothing — everything is transformed in memory first, so a failed match leaves the place untouched.
|
|
176
|
-
|
|
177
|
-
Parallel tool calls also work (responses are keyed by request id), but prefer a batch: N parallel calls are N round trips and N undo steps, a batch is one of each.
|
|
178
|
-
|
|
179
|
-
## Compared to what else exists
|
|
180
|
-
|
|
181
|
-
| | tools | transport | editor-safe writes | undo recording | live API dump | licence |
|
|
182
|
-
|---|---|---|---|---|---|---|
|
|
183
|
-
| **this** | 29 | **SSE push** | **yes** | **yes** | **yes** | MIT |
|
|
184
|
-
| [Roblox built-in](https://create.roblox.com/docs/studio/mcp) | ~27 | stdio | partial | — | n/a | closed source |
|
|
185
|
-
| [Chrrxs](https://github.com/Chrrxs/robloxstudio-mcp) | ~40 | poll | no | partial | no | MIT |
|
|
186
|
-
| [drgost1](https://github.com/drgost1/robloxstudio-mcp) | 51 | poll 500 ms | no | yes | no | MIT |
|
|
187
|
-
| [boshyxd](https://github.com/boshyxd/robloxstudio-mcp) | 43 | long-poll | no | no | no | MIT (archived) |
|
|
188
|
-
| [Roblox/studio-rust-mcp-server](https://github.com/Roblox/studio-rust-mcp-server) | 2 | HTTP | no | no | no | MIT (superseded) |
|
|
189
|
-
|
|
190
|
-
- **Push, not poll** — 50 sequential round trips: 13.6 ms mean vs 25.8 ms, 12.8 ms median vs 29.9 ms (`node scripts/latency.mjs --count 50 --compare`).
|
|
191
|
-
- **Safe script edits** — `ScriptEditorService:UpdateSourceAsync`, not `script.Source`; your unsaved editor buffer survives.
|
|
192
|
-
- **~16k tokens of schema** against 43–51 tools elsewhere. Cursor-paged, capped, `detail: concise | standard | full`.
|
|
193
|
-
- **Live API dump** — property typos get suggestions (`Anchorred` → `Anchored`).
|
|
194
|
-
|
|
195
|
-
Not built here: terrain, AI mesh and material generation.
|
|
196
|
-
|
|
197
|
-
## Security
|
|
198
|
-
|
|
199
|
-
Binds `127.0.0.1`, rejects `Origin`, and requires a header a browser cannot set cross-origin — closing the DNS-rebinding hole. HTTP permission is granted per plugin and per URL, so your experience's "Allow HTTP Requests" setting is untouched.
|
|
200
|
-
|
|
201
|
-
## Development
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
npm install
|
|
205
|
-
npm run build # TypeScript -> dist/
|
|
206
|
-
npm run build:plugin # plugin/src -> build/StudioMCP.rbxmx
|
|
207
|
-
npm run install:plugin # build + copy into the Studio plugins folder
|
|
208
|
-
npm test # plugin (Luau) + bridge (Node) tests
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Needs `luau`, `luau-compile` and `luau-analyze` from [the Luau releases](https://github.com/luau-lang/luau/releases) on `PATH` or in `tools/`.
|
|
212
|
-
|
|
213
|
-
`evals/` holds ten questions answerable only by driving a real Studio session — see [evals/README.md](evals/README.md).
|
|
214
|
-
|
|
215
|
-
## Licence
|
|
216
|
-
|
|
217
|
-
MIT.
|
|
1
|
+
# Roblox Studio MCP
|
|
2
|
+
|
|
3
|
+
MCP server for Roblox Studio: 29 tools over a push-based bridge, batched writes that undo as one step, editor-safe script edits. MIT.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
**1. The Studio plugin**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx -y @el4cteo/rbx-studio-mcp --install-plugin
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or download `StudioMCP.rbxmx` from [Releases](https://github.com/EL4CTEO/rbx-studio-mcp/releases) into your Studio plugins folder.
|
|
16
|
+
|
|
17
|
+
**2. The server**, in whichever client you use:
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary><b>Claude Code</b></summary>
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
claude mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
|
|
24
|
+
```
|
|
25
|
+
</details>
|
|
26
|
+
|
|
27
|
+
<details>
|
|
28
|
+
<summary><b>Codex CLI</b></summary>
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
codex mcp add roblox-studio -- npx -y @el4cteo/rbx-studio-mcp
|
|
32
|
+
```
|
|
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
|
+
|
|
50
|
+
<details>
|
|
51
|
+
<summary><b>Claude Desktop</b> — <code>claude_desktop_config.json</code></summary>
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"roblox-studio": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "@el4cteo/rbx-studio-mcp"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
</details>
|
|
64
|
+
|
|
65
|
+
<details>
|
|
66
|
+
<summary><b>Gemini CLI</b> — <code>~/.gemini/settings.json</code></summary>
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"roblox-studio": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "@el4cteo/rbx-studio-mcp"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
</details>
|
|
79
|
+
|
|
80
|
+
<details>
|
|
81
|
+
<summary><b>Windsurf</b> — <code>~/.codeium/windsurf/mcp_config.json</code></summary>
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
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
|
+
"mcp": {
|
|
118
|
+
"roblox-studio": {
|
|
119
|
+
"type": "local",
|
|
120
|
+
"command": ["npx", "-y", "@el4cteo/rbx-studio-mcp"],
|
|
121
|
+
"enabled": true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
</details>
|
|
127
|
+
|
|
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
|
+

|
|
133
|
+
|
|
134
|
+
Port defaults to **44755** — change with `--port` or `ROBLOX_STUDIO_MCP_PORT`, and match it in the plugin widget. Loopback only.
|
|
135
|
+
|
|
136
|
+
## Multiple agents
|
|
137
|
+
|
|
138
|
+
Register the server in as many clients as you like — the plugin connects out to one port, the first server owns it and the rest proxy through. No configuration, no second Studio connection.
|
|
139
|
+
|
|
140
|
+
Each agent keeps its own target (`set_active_studio` is per client), so two agents can work on two open places and neither can retarget the other. Pass `studioId` on a single call to reach elsewhere without changing your default.
|
|
141
|
+
|
|
142
|
+
Subagents share their parent's connection and therefore its target — a subagent calling `set_active_studio` silently retargets its parent. Give subagents an explicit `studioId` per call.
|
|
143
|
+
|
|
144
|
+
## Tools
|
|
145
|
+
|
|
146
|
+
| | |
|
|
147
|
+
|---|---|
|
|
148
|
+
| **Session** | `studio_status` `list_studios` `set_active_studio` |
|
|
149
|
+
| **Discover** | `tree` `inspect` `find` `api` |
|
|
150
|
+
| **Scripts** | `script_read` `script_edit` `script_grep` `script_create` |
|
|
151
|
+
| **Instances** | `create` `modify` `delete` `move` |
|
|
152
|
+
| **World** | `geometry` `assets` `collision` `undo` |
|
|
153
|
+
| **Run & debug** | `playtest` `execute_luau` `character` `input` `console` `debug` `performance` |
|
|
154
|
+
| **Look** | `screenshot` `viewport` `device` |
|
|
155
|
+
|
|
156
|
+
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"`.
|
|
157
|
+
|
|
158
|
+
## Batching
|
|
159
|
+
|
|
160
|
+
Every write tool takes an array — ten script edits or two hundred deletions is one call.
|
|
161
|
+
|
|
162
|
+
| tool | takes | cap |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `create` | instances, each nesting `children` to any depth | 100 |
|
|
165
|
+
| `modify` | entries, each with an unlimited list of `paths` | 100 entries |
|
|
166
|
+
| `delete` | paths | 200 |
|
|
167
|
+
| `move` | moves | 200 |
|
|
168
|
+
| `script_edit` | edits, across any number of scripts | 50 |
|
|
169
|
+
| `script_create` | scripts | 50 |
|
|
170
|
+
| `inspect` | paths | 50 |
|
|
171
|
+
| `input` | input steps, delivered in order | 40 |
|
|
172
|
+
|
|
173
|
+
`modify` caps *entries*, not targets — one entry can anchor five hundred parts, so pair it with `find` to change a whole place in one call.
|
|
174
|
+
|
|
175
|
+
Each batch is a single `ChangeHistoryService` recording: **one Ctrl+Z**. Batches are all-or-nothing — everything is transformed in memory first, so a failed match leaves the place untouched.
|
|
176
|
+
|
|
177
|
+
Parallel tool calls also work (responses are keyed by request id), but prefer a batch: N parallel calls are N round trips and N undo steps, a batch is one of each.
|
|
178
|
+
|
|
179
|
+
## Compared to what else exists
|
|
180
|
+
|
|
181
|
+
| | tools | transport | editor-safe writes | undo recording | live API dump | licence |
|
|
182
|
+
|---|---|---|---|---|---|---|
|
|
183
|
+
| **this** | 29 | **SSE push** | **yes** | **yes** | **yes** | MIT |
|
|
184
|
+
| [Roblox built-in](https://create.roblox.com/docs/studio/mcp) | ~27 | stdio | partial | — | n/a | closed source |
|
|
185
|
+
| [Chrrxs](https://github.com/Chrrxs/robloxstudio-mcp) | ~40 | poll | no | partial | no | MIT |
|
|
186
|
+
| [drgost1](https://github.com/drgost1/robloxstudio-mcp) | 51 | poll 500 ms | no | yes | no | MIT |
|
|
187
|
+
| [boshyxd](https://github.com/boshyxd/robloxstudio-mcp) | 43 | long-poll | no | no | no | MIT (archived) |
|
|
188
|
+
| [Roblox/studio-rust-mcp-server](https://github.com/Roblox/studio-rust-mcp-server) | 2 | HTTP | no | no | no | MIT (superseded) |
|
|
189
|
+
|
|
190
|
+
- **Push, not poll** — 50 sequential round trips: 13.6 ms mean vs 25.8 ms, 12.8 ms median vs 29.9 ms (`node scripts/latency.mjs --count 50 --compare`).
|
|
191
|
+
- **Safe script edits** — `ScriptEditorService:UpdateSourceAsync`, not `script.Source`; your unsaved editor buffer survives.
|
|
192
|
+
- **~16k tokens of schema** against 43–51 tools elsewhere. Cursor-paged, capped, `detail: concise | standard | full`.
|
|
193
|
+
- **Live API dump** — property typos get suggestions (`Anchorred` → `Anchored`).
|
|
194
|
+
|
|
195
|
+
Not built here: terrain, AI mesh and material generation.
|
|
196
|
+
|
|
197
|
+
## Security
|
|
198
|
+
|
|
199
|
+
Binds `127.0.0.1`, rejects `Origin`, and requires a header a browser cannot set cross-origin — closing the DNS-rebinding hole. HTTP permission is granted per plugin and per URL, so your experience's "Allow HTTP Requests" setting is untouched.
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npm install
|
|
205
|
+
npm run build # TypeScript -> dist/
|
|
206
|
+
npm run build:plugin # plugin/src -> build/StudioMCP.rbxmx
|
|
207
|
+
npm run install:plugin # build + copy into the Studio plugins folder
|
|
208
|
+
npm test # plugin (Luau) + bridge (Node) tests
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Needs `luau`, `luau-compile` and `luau-analyze` from [the Luau releases](https://github.com/luau-lang/luau/releases) on `PATH` or in `tools/`.
|
|
212
|
+
|
|
213
|
+
`evals/` holds ten questions answerable only by driving a real Studio session — see [evals/README.md](evals/README.md).
|
|
214
|
+
|
|
215
|
+
## Licence
|
|
216
|
+
|
|
217
|
+
MIT.
|
package/dist/bridge/api.js
CHANGED
|
@@ -7,17 +7,46 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
* many are connected, and its chosen Studio has no more right to leak into
|
|
8
8
|
* everyone else's calls than a proxying peer's would.
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* How often the owner reminds the bridge it is still here.
|
|
12
|
+
*
|
|
13
|
+
* The same cadence a proxying peer uses, because it is the same problem. The
|
|
14
|
+
* owner registered itself once and then never again, so the staleness reaper --
|
|
15
|
+
* which cannot tell an absent client from a quiet one -- swept the owner out of
|
|
16
|
+
* its own client list ninety seconds in, while it was actively serving. The
|
|
17
|
+
* count then read one lower than the truth, and the drop was broadcast to the
|
|
18
|
+
* Studio console as an agent having finished. Being in-process is not evidence
|
|
19
|
+
* of being alive to a rule written in timestamps; the cheapest fix is to obey
|
|
20
|
+
* the rule rather than carve an exception into it.
|
|
21
|
+
*/
|
|
22
|
+
const KEEPALIVE_MS = 30_000;
|
|
10
23
|
export class LocalBridge {
|
|
11
24
|
inner;
|
|
12
25
|
isOwner = true;
|
|
13
26
|
clientId = randomUUID();
|
|
27
|
+
keepalive;
|
|
14
28
|
constructor(inner) {
|
|
15
29
|
this.inner = inner;
|
|
30
|
+
// Announced at construction rather than on first call, for the same reason
|
|
31
|
+
// this class carries a client id at all: the process holding the port is
|
|
32
|
+
// one agent among however many are connected, and it is connected from the
|
|
33
|
+
// moment it starts, not from the moment it happens to ask for something.
|
|
34
|
+
inner.noteClient(this.clientId);
|
|
35
|
+
this.keepalive = setInterval(() => inner.noteClient(this.clientId), KEEPALIVE_MS);
|
|
36
|
+
this.keepalive.unref();
|
|
37
|
+
}
|
|
38
|
+
/** Drops this process from the bridge's client list on shutdown. */
|
|
39
|
+
goodbye() {
|
|
40
|
+
clearInterval(this.keepalive);
|
|
41
|
+
this.inner.forgetClient(this.clientId);
|
|
16
42
|
}
|
|
17
43
|
call(op, params = {}, options = {}) {
|
|
44
|
+
// Working is proof of being here, exactly as it is for a peer on /call.
|
|
45
|
+
this.inner.noteClient(this.clientId);
|
|
18
46
|
return this.inner.call(op, params, { ...options, clientId: this.clientId });
|
|
19
47
|
}
|
|
20
48
|
async sessions() {
|
|
49
|
+
this.inner.noteClient(this.clientId);
|
|
21
50
|
return {
|
|
22
51
|
list: this.inner.list(),
|
|
23
52
|
activeId: this.inner.activeId(this.clientId),
|
package/dist/bridge/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/bridge/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/bridge/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAqDzC;;;;;;;GAOG;AACH;;;;;;;;;;;GAWG;AACH,MAAM,YAAY,GAAG,MAAM,CAAC;AAE5B,MAAM,OAAO,WAAW;IAKO,KAAK;IAJzB,OAAO,GAAG,IAAI,CAAC;IACP,QAAQ,GAAG,UAAU,EAAE,CAAC;IACxB,SAAS,CAAiB;IAE3C,YAA6B,KAAa;qBAAb,KAAK;QAChC,2EAA2E;QAC3E,yEAAyE;QACzE,2EAA2E;QAC3E,yEAAyE;QACzE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC;QAClF,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,oEAAoE;IACpE,OAAO;QACL,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,CACF,EAAU,EACV,MAAM,GAA4B,EAAE,EACpC,OAAO,GAA8C,EAAE;QAEvD,wEAAwE;QACxE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAI,EAAE,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACvB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC5C,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;SACzD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,QAAgB;QAC9B,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,QAAgB,EAAE,SAAiB,EAAE,OAAgB;QACvE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;CACF"}
|
package/dist/bridge/remote.js
CHANGED
|
@@ -9,6 +9,13 @@ import { ToolError } from "../lib/errors.js";
|
|
|
9
9
|
* would share one target and silently retarget each other.
|
|
10
10
|
*/
|
|
11
11
|
export const PEER_HEADER = "x-roblox-studio-mcp-peer";
|
|
12
|
+
/**
|
|
13
|
+
* How often a peer reminds the owner it is still here.
|
|
14
|
+
*
|
|
15
|
+
* Comfortably inside the owner's 90-second staleness window, so two missed
|
|
16
|
+
* keepalives in a row are needed before a live peer is dropped by mistake.
|
|
17
|
+
*/
|
|
18
|
+
const KEEPALIVE_MS = 30_000;
|
|
12
19
|
/**
|
|
13
20
|
* Asks whatever holds `port` whether it is another copy of this server.
|
|
14
21
|
*
|
|
@@ -49,9 +56,37 @@ export class RemoteBridge {
|
|
|
49
56
|
owner;
|
|
50
57
|
isOwner = false;
|
|
51
58
|
clientId = randomUUID();
|
|
59
|
+
keepalive = null;
|
|
52
60
|
constructor(port, owner) {
|
|
53
61
|
this.port = port;
|
|
54
62
|
this.owner = owner;
|
|
63
|
+
// Announce immediately, then keep saying so. Without the keepalive the
|
|
64
|
+
// owner cannot distinguish a peer sitting idle between tasks -- which is
|
|
65
|
+
// most of any session -- from one whose process is gone.
|
|
66
|
+
void this.hello();
|
|
67
|
+
this.keepalive = setInterval(() => void this.hello(), KEEPALIVE_MS);
|
|
68
|
+
this.keepalive.unref();
|
|
69
|
+
}
|
|
70
|
+
/** Best effort: failing to register costs a badge, never a call. */
|
|
71
|
+
async hello() {
|
|
72
|
+
try {
|
|
73
|
+
await this.post("/hello", {}, 5_000);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* ignored */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async goodbye() {
|
|
80
|
+
if (this.keepalive) {
|
|
81
|
+
clearInterval(this.keepalive);
|
|
82
|
+
this.keepalive = null;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await this.post("/goodbye", {}, 5_000);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* ignored */
|
|
89
|
+
}
|
|
55
90
|
}
|
|
56
91
|
get base() {
|
|
57
92
|
return `http://127.0.0.1:${this.port}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/bridge/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAU7C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAEtD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,WAAW,EAAE;YAChE,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE;YACpC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA2B,CAAC;QAC/D,IAAI,IAAI,CAAC,MAAM,KAAK,mBAAmB;YAAE,OAAO,IAAI,CAAC;QACrD,IAAI,IAAI,CAAC,eAAe,KAAK,gBAAgB;YAAE,OAAO,IAAI,CAAC;QAC3D,OAAO,IAAqB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/bridge/remote.ts"],"names":[],"mappings":"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"}
|