@chrrxs/robloxstudio-mcp 3.0.0 → 3.0.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 +9401 -9047
- package/package.json +2 -2
- package/studio-plugin/MCPPlugin.rbxmx +400 -97
- package/studio-plugin/.Carbon.rbxm.lock +0 -0
- package/studio-plugin/Carbon.rbxm +0 -0
- package/studio-plugin/INSTALLATION.md +0 -170
- package/studio-plugin/MCPInspectorPlugin.rbxmx +0 -169759
- package/studio-plugin/default.project.json +0 -19
- package/studio-plugin/dev.project.json +0 -23
- package/studio-plugin/include/LibMP.lua +0 -156378
- package/studio-plugin/inspector-icon.png +0 -0
- package/studio-plugin/package-lock.json +0 -706
- package/studio-plugin/package.json +0 -19
- package/studio-plugin/plugin.json +0 -10
- package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
- package/studio-plugin/src/modules/ClientBroker.ts +0 -450
- package/studio-plugin/src/modules/Communication.ts +0 -601
- package/studio-plugin/src/modules/EvalBridges.ts +0 -255
- package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
- package/studio-plugin/src/modules/LuauExec.ts +0 -403
- package/studio-plugin/src/modules/Recording.ts +0 -28
- package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
- package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
- package/studio-plugin/src/modules/State.ts +0 -39
- package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
- package/studio-plugin/src/modules/UI.ts +0 -597
- package/studio-plugin/src/modules/Utils.ts +0 -527
- package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
- package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
- package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
- package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
- package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
- package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
- package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
- package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
- package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
- package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
- package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
- package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
- package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
- package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
- package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
- package/studio-plugin/src/server/index.server.ts +0 -135
- package/studio-plugin/src/types/index.d.ts +0 -57
- package/studio-plugin/tsconfig.json +0 -20
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
// Game-VM eval bridges, ported from chrrxs/roblox-mcp-primitives.
|
|
2
|
-
//
|
|
3
|
-
// Our standard `execute_luau target=server/client-N` runs in the plugin VM
|
|
4
|
-
// with a fresh ModuleScript per call. That gives a clean sandbox but means
|
|
5
|
-
// `require(SomeModule)` returns a fresh copy, not the one the running game
|
|
6
|
-
// scripts hold. So runtime-mutated module state is invisible to probes.
|
|
7
|
-
//
|
|
8
|
-
// These bridges fix that by living inside the user's game scripts. Both
|
|
9
|
-
// peers use the same symmetric shape:
|
|
10
|
-
// - Server: a Script in ServerScriptService that creates a BindableFunction.
|
|
11
|
-
// Plugin (server peer) invokes it with a fresh ModuleScript payload;
|
|
12
|
-
// require() runs inside the Script VM so it shares the running server's
|
|
13
|
-
// require cache.
|
|
14
|
-
// - Client: a LocalScript in StarterPlayer.StarterPlayerScripts that
|
|
15
|
-
// creates a BindableFunction. Plugin invokes it with a fresh ModuleScript
|
|
16
|
-
// payload; require() runs inside the LocalScript VM so it shares the
|
|
17
|
-
// game's require cache.
|
|
18
|
-
//
|
|
19
|
-
// Why ModuleScript+require on both sides (no loadstring): require'd modules
|
|
20
|
-
// run with the security level they were created at and don't need
|
|
21
|
-
// ServerScriptService.LoadStringEnabled, so eval_server_runtime works even
|
|
22
|
-
// when LoadStringEnabled=false (the default in fresh places).
|
|
23
|
-
//
|
|
24
|
-
// Lifecycle: bridge scripts are created only in running play DataModels.
|
|
25
|
-
// The server plugin peer creates the Script in runtime ServerScriptService;
|
|
26
|
-
// each client plugin peer creates its LocalScript in that client's
|
|
27
|
-
// PlayerScripts. Nothing is installed into the edit DataModel anymore.
|
|
28
|
-
// Runtime-created scripts disappear naturally when the playtest stops.
|
|
29
|
-
|
|
30
|
-
import { Players, ReplicatedStorage, RunService, ServerScriptService, StarterPlayer } from "@rbxts/services";
|
|
31
|
-
|
|
32
|
-
const ScriptEditorService = game.GetService("ScriptEditorService");
|
|
33
|
-
|
|
34
|
-
function getStarterPlayerScripts(): Instance | undefined {
|
|
35
|
-
return StarterPlayer.FindFirstChild("StarterPlayerScripts");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const SERVER_SCRIPT_NAME = "__MCP_ServerEvalBridge";
|
|
39
|
-
const CLIENT_SCRIPT_NAME = "__MCP_ClientEvalBridge";
|
|
40
|
-
|
|
41
|
-
// Public so the eval_*_runtime tool wrappers can reference the same names.
|
|
42
|
-
export const BRIDGE_NAMES = {
|
|
43
|
-
serverScript: SERVER_SCRIPT_NAME,
|
|
44
|
-
clientScript: CLIENT_SCRIPT_NAME,
|
|
45
|
-
serverLocal: "__MCP_ServerEvalLocal",
|
|
46
|
-
clientLocal: "__MCP_ClientEvalBridge",
|
|
47
|
-
} as const;
|
|
48
|
-
|
|
49
|
-
// Embedded Luau. The double `${...}` references our exported names so a
|
|
50
|
-
// rename here propagates to both the script source and the tool wrappers.
|
|
51
|
-
const SERVER_BRIDGE_SOURCE = `
|
|
52
|
-
-- Installed by @chrrxs/robloxstudio-mcp to power the eval_server_runtime MCP
|
|
53
|
-
-- tool (shared-require-cache eval on the server during playtests). Inert
|
|
54
|
-
-- outside Studio (no-ops in live games); safe to leave in place.
|
|
55
|
-
|
|
56
|
-
local ServerScriptService = game:GetService("ServerScriptService")
|
|
57
|
-
local RunService = game:GetService("RunService")
|
|
58
|
-
|
|
59
|
-
if not RunService:IsStudio() then
|
|
60
|
-
return
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
local prevBf = ServerScriptService:FindFirstChild("${BRIDGE_NAMES.serverLocal}")
|
|
64
|
-
if prevBf then prevBf:Destroy() end
|
|
65
|
-
|
|
66
|
-
local bf = Instance.new("BindableFunction")
|
|
67
|
-
bf.Name = "${BRIDGE_NAMES.serverLocal}"
|
|
68
|
-
bf.Archivable = false
|
|
69
|
-
bf.Parent = ServerScriptService
|
|
70
|
-
bf.OnInvoke = function(payload)
|
|
71
|
-
if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then
|
|
72
|
-
return { ok = false, value = "payload must be a ModuleScript instance" }
|
|
73
|
-
end
|
|
74
|
-
local ok, value = pcall(require, payload)
|
|
75
|
-
return { ok = ok, value = value }
|
|
76
|
-
end
|
|
77
|
-
`;
|
|
78
|
-
|
|
79
|
-
const CLIENT_BRIDGE_SOURCE = `
|
|
80
|
-
-- Installed by @chrrxs/robloxstudio-mcp to power the eval_client_runtime MCP
|
|
81
|
-
-- tool (shared-require-cache eval on the client during playtests). Inert
|
|
82
|
-
-- outside Studio (no-ops in live games); safe to leave in place.
|
|
83
|
-
|
|
84
|
-
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
85
|
-
local RunService = game:GetService("RunService")
|
|
86
|
-
|
|
87
|
-
if not RunService:IsStudio() then
|
|
88
|
-
return
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
local prevBf = ReplicatedStorage:FindFirstChild("${BRIDGE_NAMES.clientLocal}")
|
|
92
|
-
if prevBf then prevBf:Destroy() end
|
|
93
|
-
|
|
94
|
-
local bf = Instance.new("BindableFunction")
|
|
95
|
-
bf.Name = "${BRIDGE_NAMES.clientLocal}"
|
|
96
|
-
bf.Archivable = false
|
|
97
|
-
bf.Parent = ReplicatedStorage
|
|
98
|
-
bf.OnInvoke = function(payload)
|
|
99
|
-
if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then
|
|
100
|
-
return { ok = false, value = "payload must be a ModuleScript instance" }
|
|
101
|
-
end
|
|
102
|
-
local ok, value = pcall(require, payload)
|
|
103
|
-
return { ok = ok, value = value }
|
|
104
|
-
end
|
|
105
|
-
`;
|
|
106
|
-
|
|
107
|
-
// Stamp written onto each installed bridge Script so we can tell whether the
|
|
108
|
-
// runtime bridge currently in the play DM was produced by THIS plugin build.
|
|
109
|
-
// It's a djb2 hash of the actual bridge source plus the plugin version, so ANY
|
|
110
|
-
// change to the source (or a version bump) yields a new stamp and triggers a
|
|
111
|
-
// runtime refresh instead of keeping a stale bridge.
|
|
112
|
-
const STAMP_ATTR = "__MCPBridgeStamp";
|
|
113
|
-
|
|
114
|
-
function computeBridgeStamp(): string {
|
|
115
|
-
const combined = `${SERVER_BRIDGE_SOURCE}|${CLIENT_BRIDGE_SOURCE}`;
|
|
116
|
-
let h = 5381;
|
|
117
|
-
for (let i = 1; i <= combined.size(); i++) {
|
|
118
|
-
h = (h * 33 + string.byte(combined, i)[0]) % 2147483647;
|
|
119
|
-
}
|
|
120
|
-
// "__VERSION__" is replaced with the package version at package time
|
|
121
|
-
// (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
122
|
-
return `${tostring(h)}-__VERSION__`;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const BRIDGE_STAMP = computeBridgeStamp();
|
|
126
|
-
|
|
127
|
-
function setSource(scriptInst: Script | LocalScript, source: string): void {
|
|
128
|
-
// ScriptEditorService is the cleaner API and integrates with Studio's
|
|
129
|
-
// edit history; fall back to direct Source mutation (allowed in plugin
|
|
130
|
-
// context with PluginSecurity) if the edit service rejects the call.
|
|
131
|
-
const [seOk] = pcall(() => {
|
|
132
|
-
ScriptEditorService.UpdateSourceAsync(scriptInst, () => source);
|
|
133
|
-
});
|
|
134
|
-
if (!seOk) {
|
|
135
|
-
(scriptInst as unknown as { Source: string }).Source = source;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
interface InstallResult {
|
|
140
|
-
installed: boolean;
|
|
141
|
-
error?: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function findLegacyEditBridges(): { server?: Instance; client?: Instance } {
|
|
145
|
-
const sps = getStarterPlayerScripts();
|
|
146
|
-
return {
|
|
147
|
-
server: ServerScriptService.FindFirstChild(SERVER_SCRIPT_NAME),
|
|
148
|
-
client: sps ? sps.FindFirstChild(CLIENT_SCRIPT_NAME) : undefined,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function destroyIfPresent(parent: Instance, name: string): void {
|
|
153
|
-
const existing = parent.FindFirstChild(name);
|
|
154
|
-
if (existing) {
|
|
155
|
-
pcall(() => existing.Destroy());
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function cleanupLegacyEditBridges(): void {
|
|
160
|
-
if (RunService.IsRunning()) return;
|
|
161
|
-
const { server, client } = findLegacyEditBridges();
|
|
162
|
-
if (server) {
|
|
163
|
-
pcall(() => server.Destroy());
|
|
164
|
-
}
|
|
165
|
-
if (client) {
|
|
166
|
-
pcall(() => client.Destroy());
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function serverRuntimeBridgeReady(): boolean {
|
|
171
|
-
const scriptInst = ServerScriptService.FindFirstChild(SERVER_SCRIPT_NAME);
|
|
172
|
-
const bindable = ServerScriptService.FindFirstChild(BRIDGE_NAMES.serverLocal);
|
|
173
|
-
return scriptInst !== undefined &&
|
|
174
|
-
scriptInst.GetAttribute(STAMP_ATTR) === BRIDGE_STAMP &&
|
|
175
|
-
bindable !== undefined &&
|
|
176
|
-
bindable.IsA("BindableFunction");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function getPlayerScripts(): Instance | undefined {
|
|
180
|
-
const localPlayer = Players.LocalPlayer;
|
|
181
|
-
if (!localPlayer) return undefined;
|
|
182
|
-
let playerScripts = localPlayer.FindFirstChild("PlayerScripts");
|
|
183
|
-
if (!playerScripts) {
|
|
184
|
-
playerScripts = localPlayer.WaitForChild("PlayerScripts", 5);
|
|
185
|
-
}
|
|
186
|
-
return playerScripts;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function clientRuntimeBridgeReady(): boolean {
|
|
190
|
-
const playerScripts = getPlayerScripts();
|
|
191
|
-
if (!playerScripts) return false;
|
|
192
|
-
const scriptInst = playerScripts.FindFirstChild(CLIENT_SCRIPT_NAME);
|
|
193
|
-
const bindable = ReplicatedStorage.FindFirstChild(BRIDGE_NAMES.clientLocal);
|
|
194
|
-
return scriptInst !== undefined &&
|
|
195
|
-
scriptInst.GetAttribute(STAMP_ATTR) === BRIDGE_STAMP &&
|
|
196
|
-
bindable !== undefined &&
|
|
197
|
-
bindable.IsA("BindableFunction");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function installServerRuntimeBridge(): InstallResult {
|
|
201
|
-
if (serverRuntimeBridgeReady()) return { installed: true };
|
|
202
|
-
|
|
203
|
-
const [ok, err] = pcall(() => {
|
|
204
|
-
destroyIfPresent(ServerScriptService, SERVER_SCRIPT_NAME);
|
|
205
|
-
destroyIfPresent(ServerScriptService, BRIDGE_NAMES.serverLocal);
|
|
206
|
-
|
|
207
|
-
const serverScript = new Instance("Script");
|
|
208
|
-
serverScript.Name = SERVER_SCRIPT_NAME;
|
|
209
|
-
serverScript.Archivable = false;
|
|
210
|
-
setSource(serverScript, SERVER_BRIDGE_SOURCE);
|
|
211
|
-
serverScript.SetAttribute(STAMP_ATTR, BRIDGE_STAMP);
|
|
212
|
-
serverScript.Parent = ServerScriptService;
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
if (!ok) {
|
|
216
|
-
return { installed: false, error: tostring(err) };
|
|
217
|
-
}
|
|
218
|
-
return { installed: true };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function installClientRuntimeBridge(): InstallResult {
|
|
222
|
-
if (clientRuntimeBridgeReady()) return { installed: true };
|
|
223
|
-
|
|
224
|
-
const playerScripts = getPlayerScripts();
|
|
225
|
-
if (!playerScripts) {
|
|
226
|
-
return { installed: false, error: "Players.LocalPlayer.PlayerScripts not found - cannot install client eval bridge" };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const [ok, err] = pcall(() => {
|
|
230
|
-
destroyIfPresent(playerScripts, CLIENT_SCRIPT_NAME);
|
|
231
|
-
destroyIfPresent(ReplicatedStorage, BRIDGE_NAMES.clientLocal);
|
|
232
|
-
|
|
233
|
-
const clientScript = new Instance("LocalScript");
|
|
234
|
-
clientScript.Name = CLIENT_SCRIPT_NAME;
|
|
235
|
-
clientScript.Archivable = false;
|
|
236
|
-
setSource(clientScript, CLIENT_BRIDGE_SOURCE);
|
|
237
|
-
clientScript.SetAttribute(STAMP_ATTR, BRIDGE_STAMP);
|
|
238
|
-
clientScript.Parent = playerScripts;
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
if (!ok) {
|
|
242
|
-
return { installed: false, error: tostring(err) };
|
|
243
|
-
}
|
|
244
|
-
return { installed: true };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export function ensureRuntimeBridgeInstalled(): InstallResult {
|
|
248
|
-
if (!RunService.IsRunning()) {
|
|
249
|
-
return { installed: false, error: "Eval bridges are installed only in running play DataModels" };
|
|
250
|
-
}
|
|
251
|
-
if (RunService.IsServer()) {
|
|
252
|
-
return installServerRuntimeBridge();
|
|
253
|
-
}
|
|
254
|
-
return installClientRuntimeBridge();
|
|
255
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { HttpService } from "@rbxts/services";
|
|
2
|
-
|
|
3
|
-
interface FailureBody {
|
|
4
|
-
error?: string;
|
|
5
|
-
message?: string;
|
|
6
|
-
missingFields?: unknown;
|
|
7
|
-
request?: unknown;
|
|
8
|
-
existing?: unknown;
|
|
9
|
-
details?: unknown;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function encodeForLog(value: unknown): string {
|
|
13
|
-
const [ok, encoded] = pcall(() => HttpService.JSONEncode(value));
|
|
14
|
-
return ok ? encoded : tostring(value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function formatBody(body: string): string {
|
|
18
|
-
if (body === "") return "";
|
|
19
|
-
const [ok, decoded] = pcall(() => HttpService.JSONDecode(body));
|
|
20
|
-
if (ok && typeIs(decoded, "table")) {
|
|
21
|
-
const data = decoded as FailureBody;
|
|
22
|
-
const parts: string[] = [];
|
|
23
|
-
if (typeIs(data.error, "string") && data.error !== "") parts.push(`error=${data.error}`);
|
|
24
|
-
if (typeIs(data.message, "string") && data.message !== "") parts.push(`message=${data.message}`);
|
|
25
|
-
if (data.missingFields !== undefined) parts.push(`missingFields=${encodeForLog(data.missingFields)}`);
|
|
26
|
-
if (data.request !== undefined) parts.push(`request=${encodeForLog(data.request)}`);
|
|
27
|
-
if (data.existing !== undefined) parts.push(`existing=${encodeForLog(data.existing)}`);
|
|
28
|
-
if (data.details !== undefined) parts.push(`details=${encodeForLog(data.details)}`);
|
|
29
|
-
if (parts.size() > 0) return parts.join(" ");
|
|
30
|
-
}
|
|
31
|
-
return `body=${body}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function formatRequestFailure(url: string, ok: boolean, res: unknown): string {
|
|
35
|
-
if (!ok) {
|
|
36
|
-
return `RequestAsync threw for ${url}: ${tostring(res)}`;
|
|
37
|
-
}
|
|
38
|
-
if (res === undefined) {
|
|
39
|
-
return `RequestAsync returned no response for ${url}`;
|
|
40
|
-
}
|
|
41
|
-
const response = res as RequestAsyncResponse;
|
|
42
|
-
const statusMessage = response.StatusMessage !== "" ? ` ${response.StatusMessage}` : "";
|
|
43
|
-
const body = formatBody(response.Body);
|
|
44
|
-
const suffix = body !== "" ? `: ${body}` : "";
|
|
45
|
-
return `HTTP ${response.StatusCode}${statusMessage} from ${url}${suffix}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export = {
|
|
49
|
-
formatRequestFailure,
|
|
50
|
-
};
|