@chrrxs/robloxstudio-mcp 3.0.0 → 3.0.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.
Files changed (48) hide show
  1. package/dist/index.js +217 -51
  2. package/package.json +2 -2
  3. package/studio-plugin/MCPPlugin.rbxmx +332 -40
  4. package/studio-plugin/.Carbon.rbxm.lock +0 -0
  5. package/studio-plugin/Carbon.rbxm +0 -0
  6. package/studio-plugin/INSTALLATION.md +0 -170
  7. package/studio-plugin/MCPInspectorPlugin.rbxmx +0 -169759
  8. package/studio-plugin/default.project.json +0 -19
  9. package/studio-plugin/dev.project.json +0 -23
  10. package/studio-plugin/include/LibMP.lua +0 -156378
  11. package/studio-plugin/inspector-icon.png +0 -0
  12. package/studio-plugin/package-lock.json +0 -706
  13. package/studio-plugin/package.json +0 -19
  14. package/studio-plugin/plugin.json +0 -10
  15. package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
  16. package/studio-plugin/src/modules/ClientBroker.ts +0 -450
  17. package/studio-plugin/src/modules/Communication.ts +0 -601
  18. package/studio-plugin/src/modules/EvalBridges.ts +0 -255
  19. package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
  20. package/studio-plugin/src/modules/LuauExec.ts +0 -403
  21. package/studio-plugin/src/modules/Recording.ts +0 -28
  22. package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
  23. package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
  24. package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
  25. package/studio-plugin/src/modules/State.ts +0 -39
  26. package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
  27. package/studio-plugin/src/modules/UI.ts +0 -597
  28. package/studio-plugin/src/modules/Utils.ts +0 -527
  29. package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
  30. package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
  31. package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
  32. package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
  33. package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
  34. package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
  35. package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
  36. package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
  37. package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
  38. package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
  39. package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
  40. package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
  41. package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
  42. package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
  43. package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
  44. package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
  45. package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
  46. package/studio-plugin/src/server/index.server.ts +0 -135
  47. package/studio-plugin/src/types/index.d.ts +0 -57
  48. package/studio-plugin/tsconfig.json +0 -20
@@ -1,391 +0,0 @@
1
- import Utils from "../Utils";
2
- import Recording from "../Recording";
3
- import {
4
- sanitizeLoadedAsset,
5
- scanForbiddenImportedInstances,
6
- type AssetSanitizationOperations,
7
- type ImportedAssetInstance,
8
- } from "../AssetSanitizationPolicy";
9
-
10
- const AssetService = game.GetService("AssetService");
11
- const ChangeHistoryService = game.GetService("ChangeHistoryService");
12
- const Selection = game.GetService("Selection");
13
-
14
- const { getInstancePath, getInstanceByPath } = Utils;
15
- const { beginRecording, finishRecording } = Recording;
16
-
17
- const THIRD_PARTY_ASSET_SETTING_HINT =
18
- '\nTo load public Creator Store assets that you do not own, enable "Allow Loading Third Party Assets" in Game Settings > Security.';
19
-
20
- function destroyImportedRoot(root: Instance) {
21
- pcall(() => {
22
- root.Destroy();
23
- });
24
- }
25
-
26
- function formatAssetLoadFailure(assetId: number, loadError: unknown): string {
27
- const [settingReadable, allowInsertFreeAssets] = pcall(() => {
28
- return (AssetService as unknown as { AllowInsertFreeAssets: boolean }).AllowInsertFreeAssets;
29
- });
30
- const settingHint = settingReadable && allowInsertFreeAssets === true
31
- ? ""
32
- : THIRD_PARTY_ASSET_SETTING_HINT;
33
- return `Failed to load asset ${assetId}: ${tostring(loadError)}${settingHint}`;
34
- }
35
-
36
- const sanitizationOperations: AssetSanitizationOperations = {
37
- forceUnparent: (root: ImportedAssetInstance) => {
38
- const instance = root as Instance;
39
- const [unparentedOk] = pcall(() => {
40
- instance.Parent = undefined;
41
- });
42
- return unparentedOk && instance.Parent === undefined;
43
- },
44
- destroy: (instance: ImportedAssetInstance) => {
45
- const [destroyed] = pcall(() => {
46
- (instance as Instance).Destroy();
47
- });
48
- return destroyed;
49
- },
50
- };
51
-
52
- function insertAsset(requestData: Record<string, unknown>) {
53
- const assetId = requestData.assetId as number;
54
- const parentPath = (requestData.parentPath as string) ?? "game.Workspace";
55
- const position = requestData.position as { x: number; y: number; z: number } | undefined;
56
-
57
- if (!assetId) {
58
- return { error: "assetId is required" };
59
- }
60
-
61
- const parentInstance = getInstanceByPath(parentPath);
62
- if (!parentInstance) {
63
- return { error: `Parent instance not found: ${parentPath}` };
64
- }
65
-
66
- const recordingId = beginRecording(`Insert asset ${assetId}`);
67
-
68
- let wrapperModel: Instance | undefined;
69
- const insertedInstances: Instance[] = [];
70
- const [insertSuccess, insertResult] = pcall(() => {
71
- const [loadSuccess, loadResult] = pcall(() => {
72
- return (AssetService as unknown as { LoadAssetAsync(id: number): Instance }).LoadAssetAsync(assetId);
73
- });
74
- if (!loadSuccess || !loadResult) {
75
- error(formatAssetLoadFailure(assetId, loadResult), 0);
76
- }
77
- const loadedWrapper = loadResult as Instance;
78
- wrapperModel = loadedWrapper;
79
-
80
- const sanitization = sanitizeLoadedAsset(loadedWrapper, sanitizationOperations);
81
- if (!sanitization.success) {
82
- error(sanitization.error ?? "Asset sanitization failed.", 0);
83
- }
84
-
85
- const children = loadedWrapper.GetChildren();
86
-
87
- // Position while every sanitized child is still contained by the
88
- // unparented wrapper. Nothing reaches the DataModel before verification.
89
- if (position) {
90
- const pos = new Vector3(position.x ?? 0, position.y ?? 0, position.z ?? 0);
91
- for (const inst of children) {
92
- if (inst.IsA("BasePart")) {
93
- inst.Position = pos;
94
- } else if (inst.IsA("Model")) {
95
- if (inst.PrimaryPart) {
96
- inst.PivotTo(new CFrame(pos));
97
- } else {
98
- const firstPart = inst.FindFirstChildWhichIsA("BasePart", true);
99
- if (firstPart) {
100
- inst.PivotTo(new CFrame(pos));
101
- }
102
- }
103
- }
104
- }
105
- }
106
-
107
- // This is the first point at which imported content is parented into
108
- // Studio. Both unlimited-depth scans have already passed.
109
- for (const child of children) {
110
- child.Parent = parentInstance;
111
- insertedInstances.push(child);
112
- }
113
-
114
- pcall(() => {
115
- Selection.Set(insertedInstances);
116
- });
117
-
118
- const resultInstances = insertedInstances.map((inst) => ({
119
- name: inst.Name,
120
- className: inst.ClassName,
121
- path: getInstancePath(inst),
122
- }));
123
-
124
- return {
125
- success: true,
126
- assetId,
127
- parentPath,
128
- insertedCount: insertedInstances.size(),
129
- instances: resultInstances,
130
- sanitization: {
131
- removedScriptCount: sanitization.removedScriptCount,
132
- removedPackageLinkCount: sanitization.removedPackageLinkCount,
133
- verifiedClean: true,
134
- },
135
- };
136
- });
137
-
138
- if (!insertSuccess) {
139
- // Roll back any partially-parented children if a later operation failed.
140
- for (const inserted of insertedInstances) {
141
- pcall(() => {
142
- inserted.Destroy();
143
- });
144
- }
145
- }
146
-
147
- if (wrapperModel) {
148
- pcall(() => {
149
- wrapperModel!.Destroy();
150
- });
151
- }
152
-
153
- finishRecording(recordingId, insertSuccess);
154
-
155
- if (!insertSuccess) {
156
- return { error: `Failed to insert asset ${assetId}: ${tostring(insertResult)}` };
157
- }
158
-
159
- return insertResult;
160
- }
161
-
162
- function previewAsset(requestData: Record<string, unknown>) {
163
- const assetId = requestData.assetId as number;
164
- const includeProperties = (requestData.includeProperties as boolean) ?? false;
165
- const maxDepth = (requestData.maxDepth as number) ?? 4;
166
-
167
- if (!assetId) {
168
- return { error: "assetId is required" };
169
- }
170
-
171
- const [loadSuccess, wrapperModel] = pcall(() => {
172
- return (AssetService as unknown as { LoadAssetAsync(id: number): Instance }).LoadAssetAsync(assetId);
173
- });
174
-
175
- if (!loadSuccess || !wrapperModel) {
176
- return { error: formatAssetLoadFailure(assetId, wrapperModel) };
177
- }
178
-
179
- // Previewed assets never enter the DataModel.
180
- const [unparentedOk] = pcall(() => {
181
- (wrapperModel as Instance).Parent = undefined;
182
- });
183
- if (!unparentedOk || (wrapperModel as Instance).Parent !== undefined) {
184
- destroyImportedRoot(wrapperModel as Instance);
185
- return { error: `Failed to keep asset ${assetId} unparented for preview` };
186
- }
187
-
188
- // Security and visual-capability tracking always scan the full descendant
189
- // hierarchy. maxDepth only affects the display tree below.
190
- let totalInstances = 0;
191
- const classCounts: Record<string, number> = {};
192
- let hasAnimations = false;
193
- let hasSounds = false;
194
- let hasParticles = false;
195
- let hasVfx = false;
196
- let hasDecalsOrTextures = false;
197
- let hasMeshes = false;
198
- let hasLights = false;
199
- let hasAttachments = false;
200
- const soundReferences: Record<string, unknown>[] = [];
201
- const uniqueSoundContentIds = new Set<string>();
202
-
203
- function recordSummary(instance: Instance) {
204
- totalInstances++;
205
- const className = instance.ClassName;
206
- classCounts[className] = (classCounts[className] ?? 0) + 1;
207
-
208
- if (className === "Animation" || className === "AnimationController" || className === "Animator") hasAnimations = true;
209
- if (instance.IsA("Sound")) {
210
- hasSounds = true;
211
- const sound = instance as Sound;
212
- const soundId = sound.SoundId;
213
- if (soundId !== "") uniqueSoundContentIds.add(soundId);
214
- soundReferences.push({
215
- path: sound.GetFullName(),
216
- name: sound.Name,
217
- className,
218
- soundId,
219
- volume: sound.Volume,
220
- playbackSpeed: sound.PlaybackSpeed,
221
- looped: sound.Looped,
222
- isLoaded: sound.IsLoaded,
223
- timeLength: sound.TimeLength,
224
- rollOffMode: tostring(sound.RollOffMode),
225
- rollOffMinDistance: sound.RollOffMinDistance,
226
- rollOffMaxDistance: sound.RollOffMaxDistance,
227
- });
228
- } else if (instance.IsA("AudioPlayer")) {
229
- hasSounds = true;
230
- const audioPlayer = instance as AudioPlayer;
231
- const soundId = tostring(audioPlayer.Asset);
232
- if (soundId !== "") uniqueSoundContentIds.add(soundId);
233
- soundReferences.push({
234
- path: audioPlayer.GetFullName(),
235
- name: audioPlayer.Name,
236
- className,
237
- soundId,
238
- volume: audioPlayer.Volume,
239
- playbackSpeed: audioPlayer.PlaybackSpeed,
240
- looped: audioPlayer.Looping,
241
- autoPlay: audioPlayer.AutoPlay,
242
- isLoaded: audioPlayer.IsReady,
243
- timeLength: audioPlayer.TimeLength,
244
- });
245
- }
246
- if (
247
- instance.IsA("ParticleEmitter") ||
248
- className === "Fire" ||
249
- className === "Smoke" ||
250
- className === "Sparkles"
251
- ) {
252
- hasParticles = true;
253
- hasVfx = true;
254
- }
255
- if (instance.IsA("Beam") || instance.IsA("Trail")) hasVfx = true;
256
- if (instance.IsA("Decal") || instance.IsA("Texture")) hasDecalsOrTextures = true;
257
- if (instance.IsA("MeshPart") || instance.IsA("SpecialMesh")) hasMeshes = true;
258
- if (instance.IsA("Light")) hasLights = true;
259
- if (instance.IsA("Attachment")) hasAttachments = true;
260
- }
261
-
262
- recordSummary(wrapperModel as Instance);
263
- for (const descendant of (wrapperModel as Instance).GetDescendants()) {
264
- recordSummary(descendant);
265
- }
266
- const forbiddenScan = scanForbiddenImportedInstances(wrapperModel as Instance);
267
-
268
- function buildHierarchy(instance: Instance, depth: number): Record<string, unknown> {
269
- const className = instance.ClassName;
270
-
271
- const node: Record<string, unknown> = {
272
- name: instance.Name,
273
- className,
274
- };
275
-
276
- if (includeProperties) {
277
- const props: Record<string, unknown> = {};
278
-
279
- if (instance.IsA("BasePart")) {
280
- props.size = { x: instance.Size.X, y: instance.Size.Y, z: instance.Size.Z };
281
- props.position = { x: instance.Position.X, y: instance.Position.Y, z: instance.Position.Z };
282
- props.material = tostring(instance.Material);
283
- props.color = `${instance.Color.R}, ${instance.Color.G}, ${instance.Color.B}`;
284
- props.transparency = instance.Transparency;
285
- props.anchored = instance.Anchored;
286
- }
287
-
288
- if (instance.IsA("MeshPart")) {
289
- const meshPart = instance as MeshPart;
290
- props.meshId = meshPart.MeshId;
291
- props.textureId = meshPart.TextureID;
292
- }
293
-
294
- if (instance.IsA("Model")) {
295
- const model = instance as Model;
296
- if (model.PrimaryPart) {
297
- props.primaryPart = model.PrimaryPart.Name;
298
- }
299
- }
300
-
301
- if (className === "Decal" || className === "Texture") {
302
- const [ok, texId] = pcall(() => (instance as unknown as { Texture: string }).Texture);
303
- if (ok) props.texture = texId;
304
- }
305
-
306
- if (instance.IsA("Sound")) {
307
- props.soundId = (instance as Sound).SoundId;
308
- } else if (instance.IsA("AudioPlayer")) {
309
- props.soundId = tostring((instance as AudioPlayer).Asset);
310
- }
311
-
312
- // Only include props if there are any
313
- let hasProps = false;
314
- for (const _ of pairs(props)) {
315
- hasProps = true;
316
- break;
317
- }
318
- if (hasProps) {
319
- node.properties = props;
320
- }
321
- }
322
-
323
- if (depth < maxDepth) {
324
- const childNodes: Record<string, unknown>[] = [];
325
- for (const child of instance.GetChildren()) {
326
- childNodes.push(buildHierarchy(child, depth + 1));
327
- }
328
- if (childNodes.size() > 0) {
329
- node.children = childNodes;
330
- }
331
- } else {
332
- const childCount = instance.GetChildren().size();
333
- if (childCount > 0) {
334
- node.childCount = childCount;
335
- node.truncated = true;
336
- }
337
- }
338
-
339
- return node;
340
- }
341
-
342
- const [previewSuccess, previewResult] = pcall(() => {
343
- const hierarchyRoots: Record<string, unknown>[] = [];
344
- for (const child of (wrapperModel as Instance).GetChildren()) {
345
- hierarchyRoots.push(buildHierarchy(child, 0));
346
- }
347
-
348
- return {
349
- success: true,
350
- assetId,
351
- hierarchy: hierarchyRoots,
352
- sounds: soundReferences,
353
- summary: {
354
- totalInstances,
355
- classCounts,
356
- hasScripts: forbiddenScan.scripts.size() > 0,
357
- scriptCount: forbiddenScan.scripts.size(),
358
- hasPackageLinks: forbiddenScan.packageLinks.size() > 0,
359
- packageLinkCount: forbiddenScan.packageLinks.size(),
360
- hasAnimations,
361
- hasSounds,
362
- soundCount: soundReferences.size(),
363
- uniqueSoundContentIdCount: uniqueSoundContentIds.size(),
364
- hasParticles,
365
- hasVfx,
366
- hasDecalsOrTextures,
367
- hasMeshes,
368
- hasLights,
369
- hasAttachments,
370
- securityScanDepth: "unlimited",
371
- scriptSourceExposed: false,
372
- insertPolicy: "All LuaSourceContainer and PackageLink instances are stripped and verified before insertion.",
373
- },
374
- };
375
- });
376
-
377
- pcall(() => {
378
- (wrapperModel as Instance).Destroy();
379
- });
380
-
381
- if (!previewSuccess) {
382
- return { error: `Failed to preview asset ${assetId}: ${tostring(previewResult)}` };
383
- }
384
-
385
- return previewResult;
386
- }
387
-
388
- export = {
389
- insertAsset,
390
- previewAsset,
391
- };