@chrrxs/robloxstudio-mcp 2.16.1 → 2.16.2
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/dist/index.js +155 -23
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +159 -83
- package/studio-plugin/MCPPlugin.rbxmx +159 -83
- package/studio-plugin/src/modules/Communication.ts +41 -19
- package/studio-plugin/src/modules/ServerUrlSettings.ts +25 -12
- package/studio-plugin/src/modules/StopPlayMonitor.ts +54 -32
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Cross-DM stop_playtest signaling via plugin:SetSetting, scoped by
|
|
2
2
|
// per-instance setting key so the same Studio process can host playtests
|
|
3
3
|
// for multiple places without one place's stop_playtest yanking another's.
|
|
4
|
+
// During publish-after-connect, both "anon:<uuid>" and "place:<PlaceId>"
|
|
5
|
+
// can refer to the same Studio place, so stop requests are mirrored across
|
|
6
|
+
// both keys while the monitor waits for a matching result on either key.
|
|
4
7
|
//
|
|
5
8
|
// `plugin:SetSetting` / `plugin:GetSetting` is a per-plugin persistent store
|
|
6
9
|
// shared across every DataModel the plugin runs in (edit DMs, play-server
|
|
@@ -71,23 +74,36 @@ function init(p: Plugin): void {
|
|
|
71
74
|
// agree on the place identifier (published places: placeId; unpublished:
|
|
72
75
|
// UUID on ServerStorage's __MCPPlaceId attribute, travels with the .rbxl
|
|
73
76
|
// into the play DM).
|
|
74
|
-
function
|
|
77
|
+
function addUnique(values: string[], value: string): void {
|
|
78
|
+
if (!values.includes(value)) {
|
|
79
|
+
values.push(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function computeInstanceIds(): string[] {
|
|
84
|
+
const ids: string[] = [];
|
|
75
85
|
if (game.PlaceId !== 0) {
|
|
76
|
-
|
|
86
|
+
addUnique(ids, `place:${tostring(game.PlaceId)}`);
|
|
77
87
|
}
|
|
78
88
|
const existing = ServerStorage.GetAttribute("__MCPPlaceId");
|
|
79
89
|
if (typeIs(existing, "string") && existing !== "") {
|
|
80
|
-
|
|
90
|
+
addUnique(ids, `anon:${existing as string}`);
|
|
91
|
+
} else if (game.PlaceId === 0) {
|
|
92
|
+
const fresh = HttpService.GenerateGUID(false);
|
|
93
|
+
pcall(() => ServerStorage.SetAttribute("__MCPPlaceId", fresh));
|
|
94
|
+
addUnique(ids, `anon:${fresh}`);
|
|
81
95
|
}
|
|
82
|
-
|
|
83
|
-
pcall(() => ServerStorage.SetAttribute("__MCPPlaceId", fresh));
|
|
84
|
-
return `anon:${fresh}`;
|
|
96
|
+
return ids;
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
function settingKey(instanceId: string): string {
|
|
88
100
|
return SETTING_KEY_PREFIX + instanceId;
|
|
89
101
|
}
|
|
90
102
|
|
|
103
|
+
function settingKeys(): string[] {
|
|
104
|
+
return computeInstanceIds().map((instanceId) => settingKey(instanceId));
|
|
105
|
+
}
|
|
106
|
+
|
|
91
107
|
function readSetting(key: string): unknown {
|
|
92
108
|
if (!pluginRef) return undefined;
|
|
93
109
|
const [ok, value] = pcall(() => pluginRef!.GetSetting(key));
|
|
@@ -168,18 +184,19 @@ function startMonitor(): void {
|
|
|
168
184
|
warn("[robloxstudio-mcp] StopPlayMonitor.startMonitor called before init; skipping");
|
|
169
185
|
return;
|
|
170
186
|
}
|
|
171
|
-
const myKey = settingKey(computeInstanceId());
|
|
172
187
|
task.spawn(() => {
|
|
173
188
|
while (true) {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
for (const myKey of settingKeys()) {
|
|
190
|
+
const value = readSetting(myKey);
|
|
191
|
+
if (value === true) {
|
|
192
|
+
// Legacy boolean requests are ambiguous and may be stale from
|
|
193
|
+
// a prior crashed session. New stop requests use token payloads.
|
|
194
|
+
writeSetting(myKey, false);
|
|
195
|
+
} else {
|
|
196
|
+
const payload = decodePayload(value);
|
|
197
|
+
if (payload) {
|
|
198
|
+
handleStopRequest(myKey, payload);
|
|
199
|
+
}
|
|
183
200
|
}
|
|
184
201
|
}
|
|
185
202
|
task.wait(POLL_INTERVAL_SEC);
|
|
@@ -189,28 +206,32 @@ function startMonitor(): void {
|
|
|
189
206
|
|
|
190
207
|
function requestStop(): StopRequestResult {
|
|
191
208
|
if (!pluginRef) return { ok: false };
|
|
192
|
-
const myKey = settingKey(computeInstanceId());
|
|
193
209
|
const requestId = HttpService.GenerateGUID(false);
|
|
194
|
-
const
|
|
210
|
+
const payload: StopPayload = {
|
|
195
211
|
kind: "request",
|
|
196
212
|
id: requestId,
|
|
197
213
|
requestedAt: tick(),
|
|
198
|
-
}
|
|
214
|
+
};
|
|
215
|
+
let ok = false;
|
|
216
|
+
for (const myKey of settingKeys()) {
|
|
217
|
+
ok = writePayload(myKey, payload) || ok;
|
|
218
|
+
}
|
|
199
219
|
return { ok, requestId: ok ? requestId : undefined };
|
|
200
220
|
}
|
|
201
221
|
|
|
202
222
|
function waitForConsumption(requestId: string): StopConsumptionResult {
|
|
203
223
|
if (!pluginRef) return { ok: false, consumed: false, error: "Plugin reference is not initialized." };
|
|
204
|
-
const myKey = settingKey(computeInstanceId());
|
|
205
224
|
const start = tick();
|
|
206
225
|
while (tick() - start < WAIT_FOR_CONSUMPTION_TIMEOUT_SEC) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
for (const myKey of settingKeys()) {
|
|
227
|
+
const payload = decodePayload(readSetting(myKey));
|
|
228
|
+
if (payload && payload.kind === "result" && payload.id === requestId) {
|
|
229
|
+
return {
|
|
230
|
+
ok: payload.ok === true,
|
|
231
|
+
consumed: true,
|
|
232
|
+
error: payload.error,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
214
235
|
}
|
|
215
236
|
task.wait(WAIT_POLL_SEC);
|
|
216
237
|
}
|
|
@@ -223,12 +244,13 @@ function waitForConsumption(requestId: string): StopConsumptionResult {
|
|
|
223
244
|
|
|
224
245
|
function clearPending(requestId?: string): void {
|
|
225
246
|
if (!pluginRef) return;
|
|
226
|
-
const myKey
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
for (const myKey of settingKeys()) {
|
|
248
|
+
if (requestId !== undefined) {
|
|
249
|
+
const payload = decodePayload(readSetting(myKey));
|
|
250
|
+
if (payload && payload.id !== requestId) continue;
|
|
251
|
+
}
|
|
252
|
+
writeSetting(myKey, false);
|
|
230
253
|
}
|
|
231
|
-
writeSetting(myKey, false);
|
|
232
254
|
}
|
|
233
255
|
|
|
234
256
|
export = {
|