@chrrxs/robloxstudio-mcp 2.9.1 → 2.10.1
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 +151 -48
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +526 -190
- package/studio-plugin/MCPPlugin.rbxmx +526 -190
- package/studio-plugin/src/modules/ClientBroker.ts +125 -48
- package/studio-plugin/src/modules/Communication.ts +60 -27
- package/studio-plugin/src/modules/EvalBridges.ts +16 -53
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +138 -0
- package/studio-plugin/src/modules/UI.ts +36 -0
- package/studio-plugin/src/modules/handlers/LogHandlers.ts +15 -0
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +12 -1
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +16 -1
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +1 -13
- package/studio-plugin/src/server/index.server.ts +11 -1
- package/studio-plugin/src/types/index.d.ts +4 -0
|
@@ -294,8 +294,19 @@ function executeLuau(requestData: Record<string, unknown>) {
|
|
|
294
294
|
const runViaModuleScript = () => {
|
|
295
295
|
const m = new Instance("ModuleScript");
|
|
296
296
|
m.Name = "__MCPExecLuauPayload";
|
|
297
|
+
// Wrap user code in an IIFE so require() always gets exactly one
|
|
298
|
+
// return value. Without this, code like `print("x")` errors with
|
|
299
|
+
// "Module code did not return exactly one value" because top-level
|
|
300
|
+
// ModuleScripts must return exactly one value.
|
|
301
|
+
//
|
|
302
|
+
// The DOUBLE parens around the call are load-bearing: in Luau,
|
|
303
|
+
// `return f()` propagates whatever multi-value tuple f returns,
|
|
304
|
+
// including zero values. Outer parens adjust the call to exactly
|
|
305
|
+
// one value (the first, or nil). So `return ((f)())` always
|
|
306
|
+
// returns exactly one value, regardless of what f does.
|
|
307
|
+
const wrapped = `return ((function()\n${code}\nend)())`;
|
|
297
308
|
const [okSet, setErr] = pcall(() => {
|
|
298
|
-
(m as unknown as { Source: string }).Source =
|
|
309
|
+
(m as unknown as { Source: string }).Source = wrapped;
|
|
299
310
|
});
|
|
300
311
|
if (!okSet) {
|
|
301
312
|
m.Destroy();
|
|
@@ -96,8 +96,23 @@ function searchFiles(requestData: Record<string, unknown>) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function getPlaceInfo(_requestData: Record<string, unknown>) {
|
|
99
|
+
const dataModelName = game.Name;
|
|
100
|
+
let placeName = dataModelName;
|
|
101
|
+
|
|
102
|
+
if (game.PlaceId > 0) {
|
|
103
|
+
const MarketplaceService = game.GetService("MarketplaceService");
|
|
104
|
+
const [ok, info] = pcall(() => MarketplaceService.GetProductInfo(game.PlaceId));
|
|
105
|
+
if (ok && info !== undefined) {
|
|
106
|
+
const name = (info as { Name?: string }).Name;
|
|
107
|
+
if (typeIs(name, "string") && name !== "") {
|
|
108
|
+
placeName = name;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
return {
|
|
100
|
-
placeName
|
|
114
|
+
placeName,
|
|
115
|
+
dataModelName,
|
|
101
116
|
placeId: game.PlaceId,
|
|
102
117
|
gameId: game.GameId,
|
|
103
118
|
jobId: game.JobId,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpService, LogService } from "@rbxts/services";
|
|
2
|
-
import { installBridges, cleanupBridges
|
|
2
|
+
import { installBridges, cleanupBridges } from "../EvalBridges";
|
|
3
3
|
|
|
4
4
|
const StudioTestService = game.GetService("StudioTestService");
|
|
5
5
|
const ServerScriptService = game.GetService("ServerScriptService");
|
|
@@ -159,7 +159,6 @@ function startPlaytest(requestData: Record<string, unknown>) {
|
|
|
159
159
|
// so eval_server_runtime / eval_client_runtime work without manual setup.
|
|
160
160
|
// Bridges are cleaned up from the edit DM after the play DMs tear down.
|
|
161
161
|
const bridgeInstall = installBridges();
|
|
162
|
-
const hasLoadString = loadStringEnabled();
|
|
163
162
|
if (!bridgeInstall.installed) {
|
|
164
163
|
warn(`[MCP] Eval bridge install failed: ${bridgeInstall.error}`);
|
|
165
164
|
}
|
|
@@ -203,17 +202,6 @@ function startPlaytest(requestData: Record<string, unknown>) {
|
|
|
203
202
|
evalBridges: bridgeInstall.installed ? "installed" : `failed: ${bridgeInstall.error}`,
|
|
204
203
|
};
|
|
205
204
|
|
|
206
|
-
// Surface loadstring availability up-front so callers know whether
|
|
207
|
-
// eval_server_runtime will work before they try it. eval_client_runtime
|
|
208
|
-
// doesn't need loadstring (it uses ModuleScript+require), so this only
|
|
209
|
-
// affects the server bridge.
|
|
210
|
-
if (!hasLoadString) {
|
|
211
|
-
response.serverEvalNote =
|
|
212
|
-
"ServerScriptService.LoadStringEnabled is false. eval_server_runtime will not work " +
|
|
213
|
-
"until you enable it (ServerScriptService > Properties > LoadStringEnabled = true) " +
|
|
214
|
-
"and restart the playtest. eval_client_runtime is unaffected.";
|
|
215
|
-
}
|
|
216
|
-
|
|
217
205
|
return response;
|
|
218
206
|
}
|
|
219
207
|
|
|
@@ -2,14 +2,24 @@ import State from "../modules/State";
|
|
|
2
2
|
import UI from "../modules/UI";
|
|
3
3
|
import Communication from "../modules/Communication";
|
|
4
4
|
import ClientBroker from "../modules/ClientBroker";
|
|
5
|
+
import RuntimeLogBuffer from "../modules/RuntimeLogBuffer";
|
|
5
6
|
|
|
7
|
+
// Attach the per-peer LogService.MessageOut listener as early as possible so
|
|
8
|
+
// boot-time prints from the user's place scripts are captured. Powers the
|
|
9
|
+
// get_runtime_logs MCP tool. Idempotent; safe to call before UI.init().
|
|
10
|
+
RuntimeLogBuffer.install();
|
|
6
11
|
|
|
7
12
|
UI.init(plugin);
|
|
8
13
|
const elements = UI.getElements();
|
|
9
14
|
|
|
10
15
|
|
|
16
|
+
const ICON_DISCONNECTED = "rbxassetid://__BUTTON_ICON_DISCONNECTED__";
|
|
17
|
+
const ICON_CONNECTING = "rbxassetid://__BUTTON_ICON_CONNECTING__";
|
|
18
|
+
const ICON_CONNECTED = "rbxassetid://__BUTTON_ICON_CONNECTED__";
|
|
19
|
+
|
|
11
20
|
const toolbar = plugin.CreateToolbar("__TOOLBAR_NAME__");
|
|
12
|
-
const button = toolbar.CreateButton("__BUTTON_TITLE__", "__BUTTON_TOOLTIP__",
|
|
21
|
+
const button = toolbar.CreateButton("__BUTTON_TITLE__", "__BUTTON_TOOLTIP__", ICON_DISCONNECTED);
|
|
22
|
+
UI.setToolbarButton(button, { disconnected: ICON_DISCONNECTED, connecting: ICON_CONNECTING, connected: ICON_CONNECTED });
|
|
13
23
|
|
|
14
24
|
|
|
15
25
|
elements.connectButton.Activated.Connect(() => {
|
|
@@ -32,6 +32,10 @@ export interface PollResponse {
|
|
|
32
32
|
mcpConnected: boolean;
|
|
33
33
|
request?: RequestPayload;
|
|
34
34
|
requestId?: string;
|
|
35
|
+
// Server signals knownInstance=false when its in-memory instances map
|
|
36
|
+
// doesn't contain our instanceId (typically after an MCP process restart).
|
|
37
|
+
// The plugin re-issues /ready when it sees this.
|
|
38
|
+
knownInstance?: boolean;
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface ReadyResponse {
|