@el4cteo/rbx-studio-mcp 0.4.2 → 0.4.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.
Files changed (38) hide show
  1. package/README.md +211 -211
  2. package/dist/bridge/api.js +3 -0
  3. package/dist/bridge/api.js.map +1 -1
  4. package/dist/bridge/failover.js +13 -0
  5. package/dist/bridge/failover.js.map +1 -1
  6. package/dist/bridge/remote.js +15 -1
  7. package/dist/bridge/remote.js.map +1 -1
  8. package/dist/bridge/rpc.js +35 -6
  9. package/dist/bridge/rpc.js.map +1 -1
  10. package/dist/bridge/server.js +55 -7
  11. package/dist/bridge/server.js.map +1 -1
  12. package/dist/doctor.js +160 -0
  13. package/dist/doctor.js.map +1 -0
  14. package/dist/index.js +29 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/lib/protocol.js.map +1 -1
  17. package/dist/tools/exec.js +53 -3
  18. package/dist/tools/exec.js.map +1 -1
  19. package/dist/tools/generate.js +120 -0
  20. package/dist/tools/generate.js.map +1 -0
  21. package/dist/tools/scripts.js +20 -1
  22. package/dist/tools/scripts.js.map +1 -1
  23. package/dist/tools/world.js +203 -20
  24. package/dist/tools/world.js.map +1 -1
  25. package/package.json +2 -1
  26. package/plugin/src/Config.luau +1 -1
  27. package/plugin/src/Console.luau +233 -9
  28. package/plugin/src/Transport.luau +6 -1
  29. package/plugin/src/Visuals.luau +52 -1
  30. package/plugin/src/handlers/Assets.luau +352 -145
  31. package/plugin/src/handlers/Generate.luau +386 -0
  32. package/plugin/src/handlers/Geometry.luau +170 -0
  33. package/plugin/src/handlers/Scripts.luau +79 -1
  34. package/plugin/src/handlers/Viewport.luau +416 -302
  35. package/plugin/src/handlers/World.luau +15 -5
  36. package/plugin/src/init.server.luau +725 -674
  37. package/scripts/test-bridge.mjs +56 -0
  38. package/scripts/test-failover.mjs +136 -95
package/README.md CHANGED
@@ -1,211 +1,211 @@
1
- # Roblox Studio MCP
2
-
3
- Let an AI agent drive Roblox Studio: read your place, edit scripts, run playtests, take screenshots. 29 tools. MIT.
4
-
5
- ![The Studio MCP panel: a live activity band, a call log with latencies, and the theme drawer open](docs/rbx-studio.png)
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
- ![The Debugger Luau API beta feature toggle in Studio](docs/betatoggle.png)
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. No extra configuration.
139
-
140
- 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.
141
-
142
- ## Tools
143
-
144
- | | |
145
- |---|---|
146
- | **Session** | `studio_status` `list_studios` `set_active_studio` |
147
- | **Discover** | `tree` `inspect` `find` `api` |
148
- | **Scripts** | `script_read` `script_edit` `script_grep` `script_create` |
149
- | **Instances** | `create` `modify` `delete` `move` |
150
- | **World** | `geometry` `assets` `collision` `undo` |
151
- | **Run & debug** | `playtest` `execute_luau` `character` `input` `console` `debug` `performance` |
152
- | **Look** | `screenshot` `viewport` `device` |
153
-
154
- 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"`.
155
-
156
- ## The console panel
157
-
158
- Every call is logged with its latency, above a trace of the last forty.
159
-
160
- 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.
161
-
162
- ## Batching
163
-
164
- Every write tool takes an array — ten script edits is one call.
165
-
166
- | tool | takes | cap |
167
- |---|---|---|
168
- | `create` | instances, each nesting `children` to any depth | 100 |
169
- | `modify` | entries, each with an unlimited list of `paths` | 100 entries |
170
- | `delete` | paths | 200 |
171
- | `move` | moves | 200 |
172
- | `script_edit` | edits, across any number of scripts | 50 |
173
- | `script_create` | scripts | 50 |
174
- | `inspect` | paths | 50 |
175
- | `input` | input steps, delivered in order | 40 |
176
-
177
- `modify` caps *entries*, not targets — one entry can anchor five hundred parts.
178
-
179
- Each batch is one **Ctrl+Z**, and all-or-nothing: a failed match leaves the place untouched.
180
-
181
- ## Why this one
182
-
183
- - **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`.
184
- - **Safe script edits.** Writes go through `ScriptEditorService:UpdateSourceAsync`, so your unsaved editor buffer survives.
185
- - **One Ctrl+Z per call.** Every batch is a single undo recording.
186
- - **Property names are checked** against the running engine's API dump, so a typo comes back as `Anchorred` → `Anchored` instead of a runtime error.
187
- - **~16k tokens of schema**, cursor-paged and capped, with `detail: concise | standard | full`.
188
-
189
- Not built here: terrain, AI mesh and material generation.
190
-
191
- ## Security
192
-
193
- 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.
194
-
195
- ## Development
196
-
197
- ```bash
198
- npm install
199
- npm run build # TypeScript -> dist/
200
- npm run build:plugin # plugin/src -> build/StudioMCP.rbxmx
201
- npm run install:plugin # build + copy into the Studio plugins folder
202
- npm test # plugin (Luau) + bridge (Node) tests
203
- ```
204
-
205
- Needs `luau`, `luau-compile` and `luau-analyze` from [the Luau releases](https://github.com/luau-lang/luau/releases) on `PATH` or in `tools/`.
206
-
207
- `evals/` holds ten questions answerable only by driving a real Studio session — see [evals/README.md](evals/README.md).
208
-
209
- ## Licence
210
-
211
- MIT.
1
+ # Roblox Studio MCP
2
+
3
+ Let an AI agent drive Roblox Studio: read your place, edit scripts, run playtests, take screenshots. 30 tools. MIT.
4
+
5
+ ![The Studio MCP panel: a live activity band, a call log with latencies, and the theme drawer open](docs/rbx-studio.png)
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
+ ![The Debugger Luau API beta feature toggle in Studio](docs/betatoggle.png)
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. No extra configuration.
139
+
140
+ 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.
141
+
142
+ ## Tools
143
+
144
+ | | |
145
+ |---|---|
146
+ | **Session** | `studio_status` `list_studios` `set_active_studio` |
147
+ | **Discover** | `tree` `inspect` `find` `api` |
148
+ | **Scripts** | `script_read` `script_edit` `script_grep` `script_create` |
149
+ | **Instances** | `create` `modify` `delete` `move` |
150
+ | **World** | `geometry` `generate` `assets` `collision` `undo` |
151
+ | **Run & debug** | `playtest` `execute_luau` `character` `input` `console` `debug` `performance` |
152
+ | **Look** | `screenshot` `viewport` `device` |
153
+
154
+ 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"`.
155
+
156
+ ## The console panel
157
+
158
+ Every call is logged with its latency, above a trace of the last forty.
159
+
160
+ 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.
161
+
162
+ ## Batching
163
+
164
+ Every write tool takes an array — ten script edits is one call.
165
+
166
+ | tool | takes | cap |
167
+ |---|---|---|
168
+ | `create` | instances, each nesting `children` to any depth | 100 |
169
+ | `modify` | entries, each with an unlimited list of `paths` | 100 entries |
170
+ | `delete` | paths | 200 |
171
+ | `move` | moves | 200 |
172
+ | `script_edit` | edits, across any number of scripts | 50 |
173
+ | `script_create` | scripts | 50 |
174
+ | `inspect` | paths | 50 |
175
+ | `input` | input steps, delivered in order | 40 |
176
+
177
+ `modify` caps *entries*, not targets — one entry can anchor five hundred parts.
178
+
179
+ Each batch is one **Ctrl+Z**, and all-or-nothing: a failed match leaves the place untouched.
180
+
181
+ ## Why this one
182
+
183
+ - **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`.
184
+ - **Safe script edits.** Writes go through `ScriptEditorService:UpdateSourceAsync`, so your unsaved editor buffer survives.
185
+ - **One Ctrl+Z per call.** Every batch is a single undo recording.
186
+ - **Property names are checked** against the running engine's API dump, so a typo comes back as `Anchorred` → `Anchored` instead of a runtime error.
187
+ - **~16k tokens of schema**, cursor-paged and capped, with `detail: concise | standard | full`.
188
+
189
+ Not built here: terrain, AI mesh and material generation.
190
+
191
+ ## Security
192
+
193
+ 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.
194
+
195
+ ## Development
196
+
197
+ ```bash
198
+ npm install
199
+ npm run build # TypeScript -> dist/
200
+ npm run build:plugin # plugin/src -> build/StudioMCP.rbxmx
201
+ npm run install:plugin # build + copy into the Studio plugins folder
202
+ npm test # plugin (Luau) + bridge (Node) tests
203
+ ```
204
+
205
+ Needs `luau`, `luau-compile` and `luau-analyze` from [the Luau releases](https://github.com/luau-lang/luau/releases) on `PATH` or in `tools/`.
206
+
207
+ `evals/` holds ten questions answerable only by driving a real Studio session — see [evals/README.md](evals/README.md).
208
+
209
+ ## Licence
210
+
211
+ MIT.
@@ -59,5 +59,8 @@ export class LocalBridge {
59
59
  async notePlaceName(studioId, placeName, context) {
60
60
  this.inner.notePlaceName(studioId, placeName, context);
61
61
  }
62
+ describe(about) {
63
+ this.inner.noteClient(this.clientId, { ...about, pid: process.pid });
64
+ }
62
65
  }
63
66
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/bridge/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAoEzC;;;;;;;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;IAED,QAAQ,CAAC,KAAwB;QAC/B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACvE,CAAC;CACF"}
@@ -34,6 +34,13 @@ export class FailoverBridge {
34
34
  watch = null;
35
35
  claiming = false;
36
36
  stopped = false;
37
+ /*
38
+ * Remembered so a handover does not forget who we are. The MCP handshake
39
+ * happens once, at startup; taking the port over happens whenever the holder
40
+ * exits, which can be long after. Without this the new owner would list
41
+ * itself as "unknown" for the rest of the session.
42
+ */
43
+ about = null;
37
44
  constructor(peer, claim) {
38
45
  this.peer = peer;
39
46
  this.claim = claim;
@@ -72,6 +79,8 @@ export class FailoverBridge {
72
79
  await this.peer.goodbye();
73
80
  this.claimed = port;
74
81
  this.current = port.bridge;
82
+ if (this.about !== null)
83
+ void port.bridge.describe(this.about);
75
84
  this.stopWatching();
76
85
  // stdout belongs to the MCP transport, so this goes to stderr. Worth
77
86
  // saying: a handover explains a Studio reconnect and a lost target
@@ -136,5 +145,9 @@ export class FailoverBridge {
136
145
  notePlaceName(studioId, placeName, context) {
137
146
  return this.current.notePlaceName(studioId, placeName, context);
138
147
  }
148
+ describe(about) {
149
+ this.about = about;
150
+ return this.current.describe(about);
151
+ }
139
152
  }
140
153
  //# sourceMappingURL=failover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"failover.js","sourceRoot":"","sources":["../../src/bridge/failover.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"failover.js","sourceRoot":"","sources":["../../src/bridge/failover.ts"],"names":[],"mappings":"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"}
@@ -57,6 +57,16 @@ export class RemoteBridge {
57
57
  isOwner = false;
58
58
  clientId = randomUUID();
59
59
  keepalive = null;
60
+ /*
61
+ * Sent on every hello, not once on arrival.
62
+ *
63
+ * The owner can be replaced under us -- see FailoverBridge -- and the process
64
+ * that takes over starts with an empty client list. Repeating the description
65
+ * with each keepalive means a peer re-introduces itself to a new owner within
66
+ * one interval, where announcing once would leave it listed as "unknown" for
67
+ * the rest of the session.
68
+ */
69
+ about = null;
60
70
  constructor(port, owner) {
61
71
  this.port = port;
62
72
  this.owner = owner;
@@ -70,12 +80,16 @@ export class RemoteBridge {
70
80
  /** Best effort: failing to register costs a badge, never a call. */
71
81
  async hello() {
72
82
  try {
73
- await this.post("/hello", {}, 5_000);
83
+ await this.post("/hello", { name: this.about?.name, version: this.about?.version, pid: process.pid }, 5_000);
74
84
  }
75
85
  catch {
76
86
  /* ignored */
77
87
  }
78
88
  }
89
+ describe(about) {
90
+ this.about = about;
91
+ void this.hello();
92
+ }
79
93
  async goodbye() {
80
94
  if (this.keepalive) {
81
95
  clearInterval(this.keepalive);
@@ -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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"}
@@ -67,7 +67,7 @@ export class Bridge {
67
67
  * one Studio between two agents actually wants to see.
68
68
  */
69
69
  clients = new Map();
70
- /** Called whenever the client count changes, so the plugin can be told. */
70
+ /** Called whenever the client roster changes, so the plugin can be told. */
71
71
  onClientsChanged = null;
72
72
  /**
73
73
  * Which Studio each connected client chose, keyed by client.
@@ -154,17 +154,46 @@ export class Bridge {
154
154
  }
155
155
  // --- clients -----------------------------------------------------------
156
156
  /** Records a client as present, or refreshes one already known. */
157
- noteClient(clientId) {
157
+ noteClient(clientId, about) {
158
158
  if (clientId.length === 0)
159
159
  return;
160
- const known = this.clients.has(clientId);
161
- this.clients.set(clientId, { lastSeenAt: Date.now() });
162
- if (!known)
160
+ const known = this.clients.get(clientId);
161
+ const named = about?.name !== undefined && about.name.length > 0;
162
+ this.clients.set(clientId, {
163
+ lastSeenAt: Date.now(),
164
+ // Set once. See ClientRecord: a keepalive is not a new connection.
165
+ connectedAt: known?.connectedAt ?? Date.now(),
166
+ name: about?.name ?? known?.name ?? "unknown",
167
+ version: about?.version ?? known?.version ?? "",
168
+ pid: about?.pid ?? known?.pid ?? 0,
169
+ });
170
+ // Announced when a client arrives, and when one finally says who it is: the
171
+ // MCP handshake lands after the process has already registered, so the
172
+ // first roster is nameless and the second is the useful one.
173
+ if (known === undefined || (named && known.name !== about?.name)) {
163
174
  this.announceClients();
175
+ }
164
176
  }
165
177
  clientCount() {
166
178
  return this.clients.size;
167
179
  }
180
+ /**
181
+ * Every client currently sharing this bridge, oldest first.
182
+ *
183
+ * Ordered by arrival rather than by name so the list reads as a history: the
184
+ * one that has been here longest is the one the user most likely started on
185
+ * purpose, and a newcomer appears at the end instead of shuffling the rest.
186
+ */
187
+ clientList() {
188
+ return [...this.clients.values()]
189
+ .map((client) => ({
190
+ name: client.name,
191
+ version: client.version,
192
+ pid: client.pid,
193
+ connectedAt: client.connectedAt,
194
+ }))
195
+ .sort((left, right) => left.connectedAt - right.connectedAt);
196
+ }
168
197
  /**
169
198
  * Subscribes to changes in the client count.
170
199
  *
@@ -176,7 +205,7 @@ export class Bridge {
176
205
  this.onClientsChanged = listener;
177
206
  }
178
207
  announceClients() {
179
- this.onClientsChanged?.(this.clients.size);
208
+ this.onClientsChanged?.(this.clients.size, this.clientList());
180
209
  }
181
210
  /**
182
211
  * Records the place name a status call resolved.