@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.
Files changed (48) hide show
  1. package/dist/index.js +9401 -9047
  2. package/package.json +2 -2
  3. package/studio-plugin/MCPPlugin.rbxmx +400 -97
  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,527 +0,0 @@
1
- const ScriptEditorService = game.GetService("ScriptEditorService");
2
-
3
- const LUAU_KEYWORDS = new Set<string>([
4
- "and", "break", "continue", "do", "else", "elseif", "end", "export",
5
- "false", "for", "function", "if", "in", "local", "nil", "not", "or",
6
- "repeat", "return", "then", "true", "type", "until", "while",
7
- ]);
8
-
9
- function safeCall<T>(func: (...args: never[]) => T, ...args: never[]): T | undefined {
10
- const [success, result] = pcall(func, ...args);
11
- if (success) {
12
- return result;
13
- } else {
14
- warn(`MCP Plugin Error: ${result}`);
15
- return undefined;
16
- }
17
- }
18
-
19
- function isSimplePathSegment(segment: string): boolean {
20
- return segment.match("^[%a_][%w_]*$")[0] !== undefined && !LUAU_KEYWORDS.has(segment);
21
- }
22
-
23
- function quotePathSegment(segment: string): string {
24
- let escaped = segment.gsub("\\", "\\\\")[0];
25
- escaped = escaped.gsub("\n", "\\n")[0];
26
- escaped = escaped.gsub("\r", "\\r")[0];
27
- escaped = escaped.gsub("\t", "\\t")[0];
28
- escaped = escaped.gsub('"', '\\"')[0];
29
- return `"${escaped}"`;
30
- }
31
-
32
- function unescapePathSegment(segment: string): string {
33
- const chars: string[] = [];
34
- let i = 1;
35
- while (i <= segment.size()) {
36
- const ch = segment.sub(i, i);
37
- if (ch === "\\" && i < segment.size()) {
38
- const nextChar = segment.sub(i + 1, i + 1);
39
- if (nextChar === "n") {
40
- chars.push("\n");
41
- } else if (nextChar === "r") {
42
- chars.push("\r");
43
- } else if (nextChar === "t") {
44
- chars.push("\t");
45
- } else {
46
- chars.push(nextChar);
47
- }
48
- i += 2;
49
- } else {
50
- chars.push(ch);
51
- i += 1;
52
- }
53
- }
54
- return chars.join("");
55
- }
56
-
57
- function isCanonicalBracketStart(path: string, index: number): boolean {
58
- const quote = path.sub(index + 1, index + 1);
59
- return (quote === '"' || quote === "'") && path.sub(index - 1, index - 1) !== ".";
60
- }
61
-
62
- function parseInstancePath(path: string): string[] | undefined {
63
- let i = 1;
64
- const len = path.size();
65
- const parts: string[] = [];
66
- let current = "";
67
-
68
- if (path === "" || path === "game") return parts;
69
- if (path.sub(1, 5) === "game.") {
70
- i = 6;
71
- } else if (path.sub(1, 5) === "game[") {
72
- i = 5;
73
- }
74
-
75
- while (i <= len) {
76
- const ch = path.sub(i, i);
77
-
78
- if (ch === ".") {
79
- if (current !== "") {
80
- parts.push(current);
81
- current = "";
82
- i += 1;
83
- } else if (i > 1 && path.sub(i - 1, i - 1) === "." && i < len && path.sub(i + 1, i + 1) !== "[") {
84
- // Back-compat for previously emitted paths such as
85
- // game.ServerScriptService..dir.ReproScript, where ".dir"
86
- // was an actual instance name.
87
- current = ".";
88
- i += 1;
89
- } else {
90
- i += 1;
91
- }
92
- } else if (ch === "[" && i < len && isCanonicalBracketStart(path, i)) {
93
- if (current !== "") {
94
- parts.push(current);
95
- current = "";
96
- }
97
-
98
- const quote = path.sub(i + 1, i + 1);
99
- if (quote !== '"' && quote !== "'") return undefined;
100
- let j = i + 2;
101
- let raw = "";
102
- while (j <= len) {
103
- const c = path.sub(j, j);
104
- if (c === "\\") {
105
- if (j >= len) return undefined;
106
- raw += c + path.sub(j + 1, j + 1);
107
- j += 2;
108
- } else if (c === quote) {
109
- break;
110
- } else {
111
- raw += c;
112
- j += 1;
113
- }
114
- }
115
- if (j > len || path.sub(j, j) !== quote || path.sub(j + 1, j + 1) !== "]") return undefined;
116
- parts.push(unescapePathSegment(raw));
117
- i = j + 2;
118
- } else {
119
- current += ch;
120
- i += 1;
121
- }
122
- }
123
-
124
- if (current !== "") parts.push(current);
125
- return parts;
126
- }
127
-
128
- function getRootSegment(instance: Instance): string {
129
- if (instance.Parent === game) {
130
- const [ok, service] = pcall(() => game.GetService(instance.ClassName as keyof Services));
131
- if (ok && service === instance) {
132
- return instance.ClassName;
133
- }
134
- }
135
- return instance.Name;
136
- }
137
-
138
- function getInstancePath(instance: Instance): string {
139
- if (!instance || instance === game) {
140
- return "game";
141
- }
142
-
143
- const pathParts: string[] = [];
144
- let current: Instance | undefined = instance;
145
-
146
- while (current && current !== game) {
147
- pathParts.unshift(getRootSegment(current));
148
- current = current.Parent as Instance | undefined;
149
- }
150
-
151
- let path = "game";
152
- for (const part of pathParts) {
153
- if (isSimplePathSegment(part)) {
154
- path += `.${part}`;
155
- } else {
156
- path += `[${quotePathSegment(part)}]`;
157
- }
158
- }
159
- return path;
160
- }
161
-
162
- function getRootInstance(segment: string): Instance | undefined {
163
- const [ok, service] = pcall(() => game.GetService(segment as keyof Services));
164
- if (ok && service) return service as Instance;
165
- return game.FindFirstChild(segment);
166
- }
167
-
168
- function getInstanceByPath(path: string): Instance | undefined {
169
- const parts = parseInstancePath(path);
170
- if (parts === undefined) return undefined;
171
- if (parts.size() === 0) return game;
172
-
173
- let current: Instance | undefined = getRootInstance(parts[0]);
174
- for (let i = 1; i < parts.size(); i++) {
175
- const part = parts[i];
176
- if (!current) return undefined;
177
- current = current.FindFirstChild(part);
178
- }
179
-
180
- return current;
181
- }
182
-
183
- function splitLines(source: string): LuaTuple<[string[], boolean]> {
184
- const normalized = ((source ?? "") as string).gsub("\r\n", "\n")[0].gsub("\r", "\n")[0];
185
- const endsWithNewline = normalized.sub(-1) === "\n";
186
-
187
- const lines: string[] = [];
188
- let start = 1;
189
-
190
- while (true) {
191
- const [newlinePos] = string.find(normalized, "\n", start, true);
192
- if (newlinePos !== undefined) {
193
- lines.push(string.sub(normalized, start, newlinePos - 1));
194
- start = newlinePos + 1;
195
- } else {
196
- const remainder = string.sub(normalized, start);
197
- if (remainder !== "" || !endsWithNewline) {
198
- lines.push(remainder);
199
- }
200
- break;
201
- }
202
- }
203
-
204
- if (lines.size() === 0) {
205
- lines.push("");
206
- }
207
-
208
- return [lines, endsWithNewline] as unknown as LuaTuple<[string[], boolean]>;
209
- }
210
-
211
- function joinLines(lines: string[], hadTrailingNewline: boolean): string {
212
- let source = lines.join("\n");
213
- if (hadTrailingNewline && source.sub(-1) !== "\n") {
214
- source += "\n";
215
- }
216
- return source;
217
- }
218
-
219
- function readScriptSource(instance: LuaSourceContainer): string {
220
- const [ok, result] = pcall(() => {
221
- const doc = ScriptEditorService.FindScriptDocument(instance);
222
- if (doc) {
223
- return doc.GetText();
224
- }
225
- return undefined;
226
- });
227
- if (ok && result !== undefined) {
228
- return result;
229
- }
230
- // @rbxts/types does not expose PluginSecurity Source reads.
231
- const readableScript = instance as unknown as { Source: string };
232
- return readableScript.Source;
233
- }
234
-
235
- interface ApplyScriptSourceResult {
236
- success: boolean;
237
- method: "UpdateSourceAsync" | "direct";
238
- error?: string;
239
- }
240
-
241
- /**
242
- * Writes newSource to instance and verifies the live editor text before
243
- * reporting success. expectedSource turns line-based edits into a
244
- * compare-and-set operation: if the document changes while
245
- * UpdateSourceAsync yields or before the direct fallback runs, the write is
246
- * rejected instead of overwriting the newer draft.
247
- */
248
- function applyScriptSource(
249
- instance: LuaSourceContainer,
250
- newSource: string,
251
- expectedSource?: string,
252
- ): ApplyScriptSourceResult {
253
- const [updateSuccess, updateResult] = pcall(() => {
254
- ScriptEditorService.UpdateSourceAsync(instance, (currentSource: string) => {
255
- if (expectedSource !== undefined && currentSource !== expectedSource) {
256
- error("Script source changed while the edit was being applied; read the script again and retry");
257
- }
258
- return newSource;
259
- });
260
- if (readScriptSource(instance) !== newSource) {
261
- error("UpdateSourceAsync completed without updating the live script source");
262
- }
263
- });
264
- if (updateSuccess) {
265
- return { success: true, method: "UpdateSourceAsync" };
266
- }
267
-
268
- const [directSuccess, directResult] = pcall(() => {
269
- if (expectedSource !== undefined && readScriptSource(instance) !== expectedSource) {
270
- error("Script source changed before direct assignment; read the script again and retry");
271
- }
272
- // @rbxts/types does not expose PluginSecurity Source writes.
273
- const writableScript = instance as unknown as { Source: string };
274
- writableScript.Source = newSource;
275
- if (readScriptSource(instance) !== newSource) {
276
- error("Direct assignment completed without updating the live script source");
277
- }
278
- });
279
- if (directSuccess) {
280
- return { success: true, method: "direct" };
281
- }
282
-
283
- return {
284
- success: false,
285
- method: "direct",
286
- error: `UpdateSourceAsync failed: ${updateResult}. Direct assignment failed: ${directResult}`,
287
- };
288
- }
289
-
290
- function convertPropertyValue(instance: Instance, propertyName: string, propertyValue: unknown): unknown {
291
- if (propertyValue === undefined) return undefined;
292
-
293
- const inst = instance as unknown as Record<string, unknown>;
294
-
295
- if (typeIs(propertyValue, "table")) {
296
- const arr = propertyValue as unknown[];
297
- const tbl = propertyValue as Record<string, unknown>;
298
-
299
- if (typeIs(arr, "table") && (arr as defined[]).size() > 0) {
300
- const len = (arr as defined[]).size();
301
-
302
- if (len === 3) {
303
- const prop = propertyName.lower();
304
- if (
305
- prop === "position" || prop === "size" || prop === "orientation" ||
306
- prop === "velocity" || prop === "angularvelocity"
307
- ) {
308
- return new Vector3(
309
- (arr[0] as number) ?? 0,
310
- (arr[1] as number) ?? 0,
311
- (arr[2] as number) ?? 0,
312
- );
313
- } else if (prop === "color" || prop === "color3") {
314
- return new Color3(
315
- (arr[0] as number) ?? 0,
316
- (arr[1] as number) ?? 0,
317
- (arr[2] as number) ?? 0,
318
- );
319
- } else {
320
- const [success, currentVal] = pcall(() => inst[propertyName]);
321
- if (success) {
322
- if (typeOf(currentVal) === "Vector3") {
323
- return new Vector3(
324
- (arr[0] as number) ?? 0,
325
- (arr[1] as number) ?? 0,
326
- (arr[2] as number) ?? 0,
327
- );
328
- } else if (typeOf(currentVal) === "Color3") {
329
- return new Color3(
330
- (arr[0] as number) ?? 0,
331
- (arr[1] as number) ?? 0,
332
- (arr[2] as number) ?? 0,
333
- );
334
- }
335
- }
336
- }
337
- } else if (len === 2) {
338
- const [success, currentVal] = pcall(() => inst[propertyName]);
339
- if (success && typeOf(currentVal) === "Vector2") {
340
- return new Vector2((arr[0] as number) ?? 0, (arr[1] as number) ?? 0);
341
- }
342
- } else if (len === 4) {
343
- const [success, currentVal] = pcall(() => inst[propertyName]);
344
- if (success && typeOf(currentVal) === "UDim2") {
345
- return new UDim2(
346
- (arr[0] as number) ?? 0,
347
- (arr[1] as number) ?? 0,
348
- (arr[2] as number) ?? 0,
349
- (arr[3] as number) ?? 0,
350
- );
351
- }
352
- }
353
- }
354
-
355
- if (tbl.X !== undefined || tbl.Y !== undefined || tbl.Z !== undefined) {
356
-
357
- if (typeIs(tbl.X, "table") && typeIs(tbl.Y, "table")) {
358
- const xTbl = tbl.X as unknown as Record<string, number>;
359
- const yTbl = tbl.Y as unknown as Record<string, number>;
360
- return new UDim2(
361
- xTbl.Scale ?? 0, xTbl.Offset ?? 0,
362
- yTbl.Scale ?? 0, yTbl.Offset ?? 0,
363
- );
364
- }
365
- const [success, currentVal] = pcall(() => inst[propertyName]);
366
- if (success) {
367
- const currentType = typeOf(currentVal);
368
- if (currentType === "Vector2") {
369
- return new Vector2(
370
- (tbl.X as number) ?? 0,
371
- (tbl.Y as number) ?? 0,
372
- );
373
- }
374
- if (currentType === "Vector3") {
375
- return new Vector3(
376
- (tbl.X as number) ?? 0,
377
- (tbl.Y as number) ?? 0,
378
- (tbl.Z as number) ?? 0,
379
- );
380
- }
381
- }
382
- return new Vector3(
383
- (tbl.X as number) ?? 0,
384
- (tbl.Y as number) ?? 0,
385
- (tbl.Z as number) ?? 0,
386
- );
387
- }
388
-
389
- if (tbl.R !== undefined || tbl.G !== undefined || tbl.B !== undefined) {
390
- return new Color3(
391
- (tbl.R as number) ?? 0,
392
- (tbl.G as number) ?? 0,
393
- (tbl.B as number) ?? 0,
394
- );
395
- }
396
- }
397
-
398
- if (typeIs(propertyValue, "string")) {
399
- const [success, currentVal] = pcall(() => inst[propertyName]);
400
- if (success && typeOf(currentVal) === "EnumItem") {
401
- const enumItem = currentVal as EnumItem;
402
- const enumTypeName = tostring(enumItem.EnumType);
403
- const [enumSuccess, enumVal] = pcall(() => {
404
- return (Enum as unknown as Record<string, Record<string, EnumItem>>)[enumTypeName][propertyValue];
405
- });
406
- if (enumSuccess && enumVal) return enumVal;
407
- }
408
- if (propertyName === "BrickColor") {
409
- return new BrickColor(propertyValue as unknown as number);
410
- }
411
- if (propertyValue === "true") return true;
412
- if (propertyValue === "false") return false;
413
- }
414
-
415
- return propertyValue;
416
- }
417
-
418
- function evaluateFormula(
419
- formula: string,
420
- variables: Record<string, unknown> | undefined,
421
- instance: Instance | undefined,
422
- index: number,
423
- ): LuaTuple<[number, string | undefined]> {
424
- let value = formula;
425
-
426
- value = value.gsub("index", tostring(index))[0];
427
-
428
- if (instance && instance.IsA("BasePart")) {
429
- const pos = instance.Position;
430
- const sz = instance.Size;
431
- value = value.gsub("Position%.X", tostring(pos.X))[0];
432
- value = value.gsub("Position%.Y", tostring(pos.Y))[0];
433
- value = value.gsub("Position%.Z", tostring(pos.Z))[0];
434
- value = value.gsub("Size%.X", tostring(sz.X))[0];
435
- value = value.gsub("Size%.Y", tostring(sz.Y))[0];
436
- value = value.gsub("Size%.Z", tostring(sz.Z))[0];
437
- value = value.gsub("magnitude", tostring(pos.Magnitude))[0];
438
- }
439
-
440
- if (variables) {
441
- for (const [k, v] of pairs(variables)) {
442
- value = value.gsub(k as string, tostring(v))[0];
443
- }
444
- }
445
-
446
- value = value.gsub("sin%(([%d%.%-]+)%)", (x: string) => tostring(math.sin(tonumber(x) ?? 0)))[0];
447
- value = value.gsub("cos%(([%d%.%-]+)%)", (x: string) => tostring(math.cos(tonumber(x) ?? 0)))[0];
448
- value = value.gsub("sqrt%(([%d%.%-]+)%)", (x: string) => tostring(math.sqrt(tonumber(x) ?? 0)))[0];
449
- value = value.gsub("abs%(([%d%.%-]+)%)", (x: string) => tostring(math.abs(tonumber(x) ?? 0)))[0];
450
- value = value.gsub("floor%(([%d%.%-]+)%)", (x: string) => tostring(math.floor(tonumber(x) ?? 0)))[0];
451
- value = value.gsub("ceil%(([%d%.%-]+)%)", (x: string) => tostring(math.ceil(tonumber(x) ?? 0)))[0];
452
-
453
- const directResult = tonumber(value);
454
- if (directResult !== undefined) {
455
- return [directResult, undefined] as unknown as LuaTuple<[number, string | undefined]>;
456
- }
457
-
458
- const [success, evalResult] = pcall(() => {
459
- const num = tonumber(value);
460
- if (num !== undefined) return num;
461
-
462
- {
463
- const [a, b] = value.match("^([%d%.%-]+)%s*%*%s*([%d%.%-]+)$") as LuaTuple<[string?, string?]>;
464
- if (a && b) return (tonumber(a) ?? 0) * (tonumber(b) ?? 0);
465
- }
466
-
467
- {
468
- const [a, b] = value.match("^([%d%.%-]+)%s*%+%s*([%d%.%-]+)$") as LuaTuple<[string?, string?]>;
469
- if (a && b) return (tonumber(a) ?? 0) + (tonumber(b) ?? 0);
470
- }
471
-
472
- {
473
- const [a, b] = value.match("^([%d%.%-]+)%s*%-%s*([%d%.%-]+)$") as LuaTuple<[string?, string?]>;
474
- if (a && b) return (tonumber(a) ?? 0) - (tonumber(b) ?? 0);
475
- }
476
-
477
- {
478
- const [a, b] = value.match("^([%d%.%-]+)%s*/%s*([%d%.%-]+)$") as LuaTuple<[string?, string?]>;
479
- if (a && b) {
480
- const divisor = tonumber(b) ?? 1;
481
- if (divisor !== 0) return (tonumber(a) ?? 0) / divisor;
482
- }
483
- }
484
-
485
- error(`Unsupported formula pattern: ${value}`);
486
- });
487
-
488
- if (success && typeIs(evalResult, "number")) {
489
- return [evalResult, undefined] as unknown as LuaTuple<[number, string | undefined]>;
490
- } else {
491
- return [index, "Complex formulas not supported - using index value"] as unknown as LuaTuple<[number, string | undefined]>;
492
- }
493
- }
494
-
495
- function compareVersions(v1: string, v2: string): number {
496
- function parseVersion(v: string): number[] {
497
- const parts: number[] = [];
498
- for (const [num] of string.gmatch(v, "%d+")) {
499
- parts.push(tonumber(num) ?? 0);
500
- }
501
- return parts;
502
- }
503
-
504
- const p1 = parseVersion(v1);
505
- const p2 = parseVersion(v2);
506
- const maxLen = math.max(p1.size(), p2.size());
507
- for (let i = 0; i < maxLen; i++) {
508
- const n1 = p1[i] ?? 0;
509
- const n2 = p2[i] ?? 0;
510
- if (n1 < n2) return -1;
511
- if (n1 > n2) return 1;
512
- }
513
- return 0;
514
- }
515
-
516
- export = {
517
- safeCall,
518
- getInstancePath,
519
- getInstanceByPath,
520
- splitLines,
521
- joinLines,
522
- readScriptSource,
523
- applyScriptSource,
524
- convertPropertyValue,
525
- evaluateFormula,
526
- compareVersions,
527
- };