@chrrxs/robloxstudio-mcp 2.10.1 → 2.11.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.
@@ -86,6 +86,7 @@ local Players = _services.Players
86
86
  local ReplicatedStorage = _services.ReplicatedStorage
87
87
  local RunService = _services.RunService
88
88
  local RuntimeLogBuffer = TS.import(script, script.Parent, "RuntimeLogBuffer")
89
+ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
89
90
  -- The client peer cannot reach the MCP HTTP server - Roblox forbids
90
91
  -- HttpService:RequestAsync from the client DM even under PluginSecurity, and
91
92
  -- HttpEnabled reads as false there regardless of identity. So the server peer
@@ -107,6 +108,7 @@ local BROKER_NAME = "__MCPClientBroker"
107
108
  local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
108
109
  ["/api/execute-luau"] = true,
109
110
  ["/api/get-runtime-logs"] = true,
111
+ ["/api/get-memory-breakdown"] = true,
110
112
  }
111
113
  -- Throttle re-ready calls per proxyId so a brief window of unknownInstance
112
114
  -- polls doesn't cause a re-register stampede.
@@ -219,6 +221,9 @@ local function setupClientBroker()
219
221
  if payload and payload.endpoint == "/api/get-runtime-logs" then
220
222
  return handleGetRuntimeLogs(payload.data)
221
223
  end
224
+ if payload and payload.endpoint == "/api/get-memory-breakdown" then
225
+ return MemoryHandlers.getMemoryBreakdown(payload.data or {})
226
+ end
222
227
  if payload and payload.endpoint == "/api/execute-luau" then
223
228
  return handleExecuteLuau(payload.data)
224
229
  end
@@ -443,6 +448,8 @@ local AssetHandlers = TS.import(script, script.Parent, "handlers", "AssetHandler
443
448
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
444
449
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
445
450
  local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
451
+ local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
452
+ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
446
453
  local instanceId = HttpService:GenerateGUID(false)
447
454
  local assignedRole
448
455
  local function detectRole()
@@ -512,6 +519,9 @@ local routeMap = {
512
519
  ["/api/simulate-keyboard-input"] = InputHandlers.simulateKeyboardInput,
513
520
  ["/api/find-and-replace-in-scripts"] = ScriptHandlers.findAndReplaceInScripts,
514
521
  ["/api/get-runtime-logs"] = LogHandlers.getRuntimeLogs,
522
+ ["/api/export-rbxm"] = SerializationHandlers.exportRbxm,
523
+ ["/api/import-rbxm"] = SerializationHandlers.importRbxm,
524
+ ["/api/get-memory-breakdown"] = MemoryHandlers.getMemoryBreakdown,
515
525
  }
516
526
  local function processRequest(request)
517
527
  local endpoint = request.endpoint
@@ -2788,6 +2798,74 @@ return {
2788
2798
  </Properties>
2789
2799
  </Item>
2790
2800
  <Item class="ModuleScript" referent="12">
2801
+ <Properties>
2802
+ <string name="Name">MemoryHandlers</string>
2803
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
2804
+ local Stats = game:GetService("Stats")
2805
+ -- GetMemoryUsageMbAllCategories is gated by capability "InternalTest" and not
2806
+ -- callable from plugin context. GetMemoryUsageMbForTag is not - so we iterate
2807
+ -- Enum.DeveloperMemoryTag and ask per-tag.
2808
+ local function getMemoryBreakdown(requestData)
2809
+ if not Stats.MemoryTrackingEnabled then
2810
+ return {
2811
+ error = "MemoryTrackingEnabled is false on this peer",
2812
+ }
2813
+ end
2814
+ local requested = requestData.tags
2815
+ local _result
2816
+ if requested and #requested > 0 then
2817
+ local _set = {}
2818
+ for _, _v in requested do
2819
+ _set[_v] = true
2820
+ end
2821
+ _result = _set
2822
+ else
2823
+ _result = nil
2824
+ end
2825
+ local requestedSet = _result
2826
+ local categories = {}
2827
+ for _, item in Enum.DeveloperMemoryTag:GetEnumItems() do
2828
+ local name = item.Name
2829
+ if requestedSet and not (requestedSet[name] ~= nil) then
2830
+ continue
2831
+ end
2832
+ local ok, mb = pcall(function()
2833
+ return Stats:GetMemoryUsageMbForTag(item)
2834
+ end)
2835
+ categories[name] = if ok then mb else 0
2836
+ end
2837
+ local unknownTags = {}
2838
+ if requestedSet then
2839
+ local known = {}
2840
+ for _, i in Enum.DeveloperMemoryTag:GetEnumItems() do
2841
+ local _name = i.Name
2842
+ known[_name] = true
2843
+ end
2844
+ for t in requestedSet do
2845
+ if not (known[t] ~= nil) then
2846
+ table.insert(unknownTags, t)
2847
+ categories[t] = 0
2848
+ end
2849
+ end
2850
+ end
2851
+ local result = {
2852
+ total_mb = Stats:GetTotalMemoryUsageMb(),
2853
+ categories = categories,
2854
+ memory_tracking_enabled = true,
2855
+ timestamp = DateTime.now().UnixTimestampMillis,
2856
+ }
2857
+ if #unknownTags > 0 then
2858
+ result.unknown_tags = unknownTags
2859
+ end
2860
+ return result
2861
+ end
2862
+ return {
2863
+ getMemoryBreakdown = getMemoryBreakdown,
2864
+ }
2865
+ ]]></string>
2866
+ </Properties>
2867
+ </Item>
2868
+ <Item class="ModuleScript" referent="13">
2791
2869
  <Properties>
2792
2870
  <string name="Name">MetadataHandlers</string>
2793
2871
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3424,7 +3502,7 @@ return {
3424
3502
  ]]></string>
3425
3503
  </Properties>
3426
3504
  </Item>
3427
- <Item class="ModuleScript" referent="13">
3505
+ <Item class="ModuleScript" referent="14">
3428
3506
  <Properties>
3429
3507
  <string name="Name">PropertyHandlers</string>
3430
3508
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3676,7 +3754,7 @@ return {
3676
3754
  ]]></string>
3677
3755
  </Properties>
3678
3756
  </Item>
3679
- <Item class="ModuleScript" referent="14">
3757
+ <Item class="ModuleScript" referent="15">
3680
3758
  <Properties>
3681
3759
  <string name="Name">QueryHandlers</string>
3682
3760
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4718,7 +4796,7 @@ return {
4718
4796
  ]]></string>
4719
4797
  </Properties>
4720
4798
  </Item>
4721
- <Item class="ModuleScript" referent="15">
4799
+ <Item class="ModuleScript" referent="16">
4722
4800
  <Properties>
4723
4801
  <string name="Name">ScriptHandlers</string>
4724
4802
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5414,7 +5492,193 @@ return {
5414
5492
  ]]></string>
5415
5493
  </Properties>
5416
5494
  </Item>
5417
- <Item class="ModuleScript" referent="16">
5495
+ <Item class="ModuleScript" referent="17">
5496
+ <Properties>
5497
+ <string name="Name">SerializationHandlers</string>
5498
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
5499
+ local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
5500
+ local RunService = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services").RunService
5501
+ local Utils = TS.import(script, script.Parent.Parent, "Utils")
5502
+ local Recording = TS.import(script, script.Parent.Parent, "Recording")
5503
+ -- SerializationService:SerializeInstancesAsync / DeserializeInstancesAsync were
5504
+ -- added in engine v668 and are PluginSecurity. They are not in @rbxts/types yet,
5505
+ -- so we resolve the service through an untyped GetService cast and treat the
5506
+ -- methods as opaque (buffer in / buffer out).
5507
+ local SerializationService = game:GetService("SerializationService")
5508
+ -- EncodingService:Base64Encode / Base64Decode take and return `buffer` (not
5509
+ -- `string`). The signature is in @rbxts/types under None.d.ts so a normal
5510
+ -- GetService("EncodingService") would already give correct types, but @rbxts
5511
+ -- generates a per-service nominal interface and roblox.d.ts doesn't re-export
5512
+ -- EncodingService from the services barrel module - so the typed cast below
5513
+ -- matches what GetService would give us if it did.
5514
+ local EncodingService = game:GetService("EncodingService")
5515
+ local _binding = Utils
5516
+ local getInstanceByPath = _binding.getInstanceByPath
5517
+ local getInstancePath = _binding.getInstancePath
5518
+ local _binding_1 = Recording
5519
+ local beginRecording = _binding_1.beginRecording
5520
+ local finishRecording = _binding_1.finishRecording
5521
+ local function exportRbxm(requestData)
5522
+ local instancePaths = requestData.instance_paths
5523
+ if not instancePaths or not (type(instancePaths) == "table") or #instancePaths == 0 then
5524
+ return {
5525
+ error = "instance_paths must be a non-empty array",
5526
+ }
5527
+ end
5528
+ local instances = {}
5529
+ for _, p in instancePaths do
5530
+ local inst = getInstanceByPath(p)
5531
+ if not inst then
5532
+ return {
5533
+ error = `instance not found: {p}`,
5534
+ }
5535
+ end
5536
+ table.insert(instances, inst)
5537
+ end
5538
+ local serializeOk, serializeResult = pcall(function()
5539
+ return SerializationService:SerializeInstancesAsync(instances)
5540
+ end)
5541
+ if not serializeOk then
5542
+ return {
5543
+ error = `SerializeInstancesAsync failed: {tostring(serializeResult)}`,
5544
+ }
5545
+ end
5546
+ local buf = serializeResult
5547
+ local encodeOk, encodeResult = pcall(function()
5548
+ return EncodingService:Base64Encode(buf)
5549
+ end)
5550
+ if not encodeOk then
5551
+ return {
5552
+ error = `EncodingService:Base64Encode failed: {tostring(encodeResult)}`,
5553
+ }
5554
+ end
5555
+ -- Base64Encode returns a buffer of ASCII bytes; convert to a Lua string so
5556
+ -- HttpService:JSONEncode (called by the harness in Communication.ts) accepts
5557
+ -- it. Base64 is by definition pure ASCII so this round-trips cleanly.
5558
+ local base64Str = buffer.tostring(encodeResult)
5559
+ return {
5560
+ base64 = base64Str,
5561
+ instance_count = #instances,
5562
+ }
5563
+ end
5564
+ local function importRbxm(requestData)
5565
+ local b64 = requestData.base64
5566
+ local parentPath = requestData.parent_path
5567
+ local _condition = (requestData.source_label)
5568
+ if _condition == nil then
5569
+ _condition = "unknown"
5570
+ end
5571
+ local sourceLabel = _condition
5572
+ if not (b64 ~= "" and b64) or not (type(b64) == "string") then
5573
+ return {
5574
+ error = "base64 payload is required",
5575
+ }
5576
+ end
5577
+ if not (parentPath ~= "" and parentPath) or not (type(parentPath) == "string") then
5578
+ return {
5579
+ error = "parent_path is required",
5580
+ }
5581
+ end
5582
+ local parentInstance = getInstanceByPath(parentPath)
5583
+ if not parentInstance then
5584
+ return {
5585
+ error = `parent instance not found: {parentPath}`,
5586
+ }
5587
+ end
5588
+ -- b64 is an ASCII-only Lua string from the wire; lift it into a buffer for
5589
+ -- EncodingService:Base64Decode, which returns a buffer of raw rbxm bytes
5590
+ -- ready for DeserializeInstancesAsync.
5591
+ local b64BufOk, b64BufResult = pcall(function()
5592
+ return buffer.fromstring(b64)
5593
+ end)
5594
+ if not b64BufOk then
5595
+ return {
5596
+ error = `buffer.fromstring(base64) failed: {tostring(b64BufResult)}`,
5597
+ }
5598
+ end
5599
+ local decodeOk, decodeResult = pcall(function()
5600
+ return EncodingService:Base64Decode(b64BufResult)
5601
+ end)
5602
+ if not decodeOk then
5603
+ return {
5604
+ error = `EncodingService:Base64Decode failed: {tostring(decodeResult)}`,
5605
+ }
5606
+ end
5607
+ local buf = decodeResult
5608
+ local deserOk, deserResult = pcall(function()
5609
+ return SerializationService:DeserializeInstancesAsync(buf)
5610
+ end)
5611
+ if not deserOk then
5612
+ return {
5613
+ error = `DeserializeInstancesAsync failed: {tostring(deserResult)}`,
5614
+ }
5615
+ end
5616
+ local deserialized = deserResult
5617
+ -- All-or-nothing parenting. Track every instance we've attached and roll back
5618
+ -- (unparent + Destroy) if any later one fails - partial imports leave the DM
5619
+ -- in a worse state than failing cleanly.
5620
+ local isEdit = not RunService:IsRunning()
5621
+ local recordingId = if isEdit then beginRecording(`Import rbxm`) else nil
5622
+ local attached = {}
5623
+ local failureMessage
5624
+ for _, inst in deserialized do
5625
+ local parentOk, parentErr = pcall(function()
5626
+ inst.Parent = parentInstance
5627
+ end)
5628
+ if not parentOk then
5629
+ failureMessage = `failed to parent {inst.Name} ({inst.ClassName}) under {parentPath}: {tostring(parentErr)}`
5630
+ break
5631
+ end
5632
+ table.insert(attached, inst)
5633
+ end
5634
+ if failureMessage ~= nil then
5635
+ for _, inst in attached do
5636
+ pcall(function()
5637
+ inst.Parent = nil
5638
+ inst:Destroy()
5639
+ end)
5640
+ end
5641
+ -- Also destroy any unparented deserialized instances so they don't leak.
5642
+ for _, inst in deserialized do
5643
+ if inst.Parent == nil then
5644
+ pcall(function()
5645
+ return inst:Destroy()
5646
+ end)
5647
+ end
5648
+ end
5649
+ finishRecording(recordingId, false)
5650
+ return {
5651
+ error = failureMessage,
5652
+ }
5653
+ end
5654
+ local names = {}
5655
+ local paths = {}
5656
+ for _, inst in attached do
5657
+ local _name = inst.Name
5658
+ table.insert(names, _name)
5659
+ local _arg0 = getInstancePath(inst)
5660
+ table.insert(paths, _arg0)
5661
+ end
5662
+ -- The recording shows "MCP: Import rbxm" in Studio's undo stack -
5663
+ -- ChangeHistoryService doesn't expose a way to set a richer displayName
5664
+ -- after TryBeginRecording, so the count/source only land in the JSON response.
5665
+ finishRecording(recordingId, true)
5666
+ return {
5667
+ instance_count = #attached,
5668
+ instance_names = names,
5669
+ instance_paths = paths,
5670
+ parent_path = parentPath,
5671
+ source = sourceLabel,
5672
+ }
5673
+ end
5674
+ return {
5675
+ exportRbxm = exportRbxm,
5676
+ importRbxm = importRbxm,
5677
+ }
5678
+ ]]></string>
5679
+ </Properties>
5680
+ </Item>
5681
+ <Item class="ModuleScript" referent="18">
5418
5682
  <Properties>
5419
5683
  <string name="Name">TestHandlers</string>
5420
5684
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5724,7 +5988,7 @@ return {
5724
5988
  </Properties>
5725
5989
  </Item>
5726
5990
  </Item>
5727
- <Item class="ModuleScript" referent="17">
5991
+ <Item class="ModuleScript" referent="19">
5728
5992
  <Properties>
5729
5993
  <string name="Name">Recording</string>
5730
5994
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5754,7 +6018,7 @@ return {
5754
6018
  ]]></string>
5755
6019
  </Properties>
5756
6020
  </Item>
5757
- <Item class="ModuleScript" referent="18">
6021
+ <Item class="ModuleScript" referent="20">
5758
6022
  <Properties>
5759
6023
  <string name="Name">RuntimeLogBuffer</string>
5760
6024
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5935,11 +6199,11 @@ return {
5935
6199
  ]]></string>
5936
6200
  </Properties>
5937
6201
  </Item>
5938
- <Item class="ModuleScript" referent="19">
6202
+ <Item class="ModuleScript" referent="21">
5939
6203
  <Properties>
5940
6204
  <string name="Name">State</string>
5941
6205
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
5942
- local CURRENT_VERSION = "2.10.1"
6206
+ local CURRENT_VERSION = "2.11.1"
5943
6207
  local MAX_CONNECTIONS = 5
5944
6208
  local BASE_PORT = 58741
5945
6209
  local activeTabIndex = 0
@@ -6031,7 +6295,7 @@ return {
6031
6295
  ]]></string>
6032
6296
  </Properties>
6033
6297
  </Item>
6034
- <Item class="ModuleScript" referent="20">
6298
+ <Item class="ModuleScript" referent="22">
6035
6299
  <Properties>
6036
6300
  <string name="Name">UI</string>
6037
6301
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6315,7 +6579,7 @@ local function init(pluginRef)
6315
6579
  creditsLabel.Position = UDim2.new(0, 10, 0, 23)
6316
6580
  creditsLabel.BackgroundTransparency = 1
6317
6581
  creditsLabel.RichText = true
6318
- creditsLabel.Text = '<font color="#999999">by</font> <font color="#CCCCCC">@BoshyDx</font> <font color="#666666">|</font> <font color="#999999">discord</font> <font color="#CCCCCC">boshyz</font>'
6582
+ creditsLabel.Text = '<font color="#999999">github</font> <font color="#CCCCCC">Chrrxs/robloxstudio-mcp</font>'
6319
6583
  creditsLabel.TextColor3 = C.muted
6320
6584
  creditsLabel.TextSize = 8
6321
6585
  creditsLabel.Font = Enum.Font.GothamMedium
@@ -6782,7 +7046,7 @@ return {
6782
7046
  ]]></string>
6783
7047
  </Properties>
6784
7048
  </Item>
6785
- <Item class="ModuleScript" referent="21">
7049
+ <Item class="ModuleScript" referent="23">
6786
7050
  <Properties>
6787
7051
  <string name="Name">Utils</string>
6788
7052
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7312,11 +7576,11 @@ return {
7312
7576
  </Properties>
7313
7577
  </Item>
7314
7578
  </Item>
7315
- <Item class="Folder" referent="25">
7579
+ <Item class="Folder" referent="27">
7316
7580
  <Properties>
7317
7581
  <string name="Name">include</string>
7318
7582
  </Properties>
7319
- <Item class="ModuleScript" referent="22">
7583
+ <Item class="ModuleScript" referent="24">
7320
7584
  <Properties>
7321
7585
  <string name="Name">Promise</string>
7322
7586
  <string name="Source"><![CDATA[--[[
@@ -9390,7 +9654,7 @@ return Promise
9390
9654
  ]]></string>
9391
9655
  </Properties>
9392
9656
  </Item>
9393
- <Item class="ModuleScript" referent="23">
9657
+ <Item class="ModuleScript" referent="25">
9394
9658
  <Properties>
9395
9659
  <string name="Name">RuntimeLib</string>
9396
9660
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -9657,15 +9921,15 @@ return TS
9657
9921
  </Properties>
9658
9922
  </Item>
9659
9923
  </Item>
9660
- <Item class="Folder" referent="26">
9924
+ <Item class="Folder" referent="28">
9661
9925
  <Properties>
9662
9926
  <string name="Name">node_modules</string>
9663
9927
  </Properties>
9664
- <Item class="Folder" referent="27">
9928
+ <Item class="Folder" referent="29">
9665
9929
  <Properties>
9666
9930
  <string name="Name">@rbxts</string>
9667
9931
  </Properties>
9668
- <Item class="ModuleScript" referent="24">
9932
+ <Item class="ModuleScript" referent="26">
9669
9933
  <Properties>
9670
9934
  <string name="Name">services</string>
9671
9935
  <string name="Source"><![CDATA[return setmetatable({}, {
@@ -1,5 +1,6 @@
1
1
  import { HttpService, Players, ReplicatedStorage, RunService } from "@rbxts/services";
2
2
  import RuntimeLogBuffer from "./RuntimeLogBuffer";
3
+ import MemoryHandlers from "./handlers/MemoryHandlers";
3
4
 
4
5
  // The client peer cannot reach the MCP HTTP server - Roblox forbids
5
6
  // HttpService:RequestAsync from the client DM even under PluginSecurity, and
@@ -45,6 +46,7 @@ interface ExecuteResult {
45
46
  const CLIENT_BROKER_ALLOWED_ENDPOINTS = new Set<string>([
46
47
  "/api/execute-luau",
47
48
  "/api/get-runtime-logs",
49
+ "/api/get-memory-breakdown",
48
50
  ]);
49
51
 
50
52
  interface ReadyResponseBody {
@@ -145,6 +147,9 @@ function setupClientBroker() {
145
147
  if (payload && payload.endpoint === "/api/get-runtime-logs") {
146
148
  return handleGetRuntimeLogs(payload.data);
147
149
  }
150
+ if (payload && payload.endpoint === "/api/get-memory-breakdown") {
151
+ return MemoryHandlers.getMemoryBreakdown(payload.data ?? {});
152
+ }
148
153
  if (payload && payload.endpoint === "/api/execute-luau") {
149
154
  return handleExecuteLuau(payload.data);
150
155
  }
@@ -13,6 +13,8 @@ import AssetHandlers from "./handlers/AssetHandlers";
13
13
  import CaptureHandlers from "./handlers/CaptureHandlers";
14
14
  import InputHandlers from "./handlers/InputHandlers";
15
15
  import LogHandlers from "./handlers/LogHandlers";
16
+ import SerializationHandlers from "./handlers/SerializationHandlers";
17
+ import MemoryHandlers from "./handlers/MemoryHandlers";
16
18
  import { Connection, RequestPayload, PollResponse, ReadyResponse } from "../types";
17
19
 
18
20
  const instanceId = HttpService.GenerateGUID(false);
@@ -95,6 +97,11 @@ const routeMap: Record<string, Handler> = {
95
97
  "/api/find-and-replace-in-scripts": ScriptHandlers.findAndReplaceInScripts,
96
98
 
97
99
  "/api/get-runtime-logs": LogHandlers.getRuntimeLogs,
100
+
101
+ "/api/export-rbxm": SerializationHandlers.exportRbxm,
102
+ "/api/import-rbxm": SerializationHandlers.importRbxm,
103
+
104
+ "/api/get-memory-breakdown": MemoryHandlers.getMemoryBreakdown,
98
105
  };
99
106
 
100
107
  function processRequest(request: RequestPayload): unknown {
@@ -301,7 +301,7 @@ function init(pluginRef: Plugin) {
301
301
  creditsLabel.Position = new UDim2(0, 10, 0, 23);
302
302
  creditsLabel.BackgroundTransparency = 1;
303
303
  creditsLabel.RichText = true;
304
- creditsLabel.Text = '<font color="#999999">by</font> <font color="#CCCCCC">@BoshyDx</font> <font color="#666666">|</font> <font color="#999999">discord</font> <font color="#CCCCCC">boshyz</font>';
304
+ creditsLabel.Text = '<font color="#999999">github</font> <font color="#CCCCCC">Chrrxs/robloxstudio-mcp</font>';
305
305
  creditsLabel.TextColor3 = C.muted;
306
306
  creditsLabel.TextSize = 8;
307
307
  creditsLabel.Font = Enum.Font.GothamMedium;
@@ -0,0 +1,44 @@
1
+ const Stats = game.GetService("Stats");
2
+
3
+ // GetMemoryUsageMbAllCategories is gated by capability "InternalTest" and not
4
+ // callable from plugin context. GetMemoryUsageMbForTag is not - so we iterate
5
+ // Enum.DeveloperMemoryTag and ask per-tag.
6
+ function getMemoryBreakdown(requestData: Record<string, unknown>): unknown {
7
+ if (!Stats.MemoryTrackingEnabled) {
8
+ return { error: "MemoryTrackingEnabled is false on this peer" };
9
+ }
10
+
11
+ const requested = requestData.tags as string[] | undefined;
12
+ const requestedSet = requested && requested.size() > 0 ? new Set(requested) : undefined;
13
+
14
+ const categories: Record<string, number> = {};
15
+ for (const item of Enum.DeveloperMemoryTag.GetEnumItems()) {
16
+ const name = item.Name;
17
+ if (requestedSet && !requestedSet.has(name)) continue;
18
+ const [ok, mb] = pcall(() => Stats.GetMemoryUsageMbForTag(item));
19
+ categories[name] = ok ? (mb as number) : 0;
20
+ }
21
+
22
+ const unknownTags: string[] = [];
23
+ if (requestedSet) {
24
+ const known = new Set<string>();
25
+ for (const i of Enum.DeveloperMemoryTag.GetEnumItems()) known.add(i.Name);
26
+ for (const t of requestedSet) {
27
+ if (!known.has(t)) {
28
+ unknownTags.push(t);
29
+ categories[t] = 0;
30
+ }
31
+ }
32
+ }
33
+
34
+ const result: Record<string, unknown> = {
35
+ total_mb: Stats.GetTotalMemoryUsageMb(),
36
+ categories,
37
+ memory_tracking_enabled: true,
38
+ timestamp: DateTime.now().UnixTimestampMillis,
39
+ };
40
+ if (unknownTags.size() > 0) result.unknown_tags = unknownTags;
41
+ return result;
42
+ }
43
+
44
+ export = { getMemoryBreakdown };