@chrrxs/robloxstudio-mcp 2.10.0 → 2.11.0

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.
@@ -17,8 +17,16 @@ local RuntimeLogBuffer = TS.import(script, script, "modules", "RuntimeLogBuffer"
17
17
  RuntimeLogBuffer.install()
18
18
  UI.init(plugin)
19
19
  local elements = UI.getElements()
20
+ local ICON_DISCONNECTED = "rbxassetid://75876056391496"
21
+ local ICON_CONNECTING = "rbxassetid://71302583919560"
22
+ local ICON_CONNECTED = "rbxassetid://130958234173611"
20
23
  local toolbar = plugin:CreateToolbar("MCP Integration")
21
- local button = toolbar:CreateButton("MCP Server", "Connect to MCP Server for AI Integration", "rbxassetid://10734944444")
24
+ local button = toolbar:CreateButton("MCP Server", "Connect to MCP Server for AI Integration", ICON_DISCONNECTED)
25
+ UI.setToolbarButton(button, {
26
+ disconnected = ICON_DISCONNECTED,
27
+ connecting = ICON_CONNECTING,
28
+ connected = ICON_CONNECTED,
29
+ })
22
30
  elements.connectButton.Activated:Connect(function()
23
31
  local conn = State.getActiveConnection()
24
32
  if conn and conn.isActive then
@@ -78,6 +86,7 @@ local Players = _services.Players
78
86
  local ReplicatedStorage = _services.ReplicatedStorage
79
87
  local RunService = _services.RunService
80
88
  local RuntimeLogBuffer = TS.import(script, script.Parent, "RuntimeLogBuffer")
89
+ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
81
90
  -- The client peer cannot reach the MCP HTTP server - Roblox forbids
82
91
  -- HttpService:RequestAsync from the client DM even under PluginSecurity, and
83
92
  -- HttpEnabled reads as false there regardless of identity. So the server peer
@@ -99,6 +108,7 @@ local BROKER_NAME = "__MCPClientBroker"
99
108
  local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
100
109
  ["/api/execute-luau"] = true,
101
110
  ["/api/get-runtime-logs"] = true,
111
+ ["/api/get-memory-breakdown"] = true,
102
112
  }
103
113
  -- Throttle re-ready calls per proxyId so a brief window of unknownInstance
104
114
  -- polls doesn't cause a re-register stampede.
@@ -211,6 +221,9 @@ local function setupClientBroker()
211
221
  if payload and payload.endpoint == "/api/get-runtime-logs" then
212
222
  return handleGetRuntimeLogs(payload.data)
213
223
  end
224
+ if payload and payload.endpoint == "/api/get-memory-breakdown" then
225
+ return MemoryHandlers.getMemoryBreakdown(payload.data or {})
226
+ end
214
227
  if payload and payload.endpoint == "/api/execute-luau" then
215
228
  return handleExecuteLuau(payload.data)
216
229
  end
@@ -435,6 +448,8 @@ local AssetHandlers = TS.import(script, script.Parent, "handlers", "AssetHandler
435
448
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
436
449
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
437
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")
438
453
  local instanceId = HttpService:GenerateGUID(false)
439
454
  local assignedRole
440
455
  local function detectRole()
@@ -504,6 +519,9 @@ local routeMap = {
504
519
  ["/api/simulate-keyboard-input"] = InputHandlers.simulateKeyboardInput,
505
520
  ["/api/find-and-replace-in-scripts"] = ScriptHandlers.findAndReplaceInScripts,
506
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,
507
525
  }
508
526
  local function processRequest(request)
509
527
  local endpoint = request.endpoint
@@ -603,6 +621,9 @@ local function pollForRequests(connIndex)
603
621
  conn.isPolling = false
604
622
  local ui = UI.getElements()
605
623
  UI.updateTabDot(connIndex)
624
+ if connIndex == State.getActiveTabIndex() then
625
+ UI.updateToolbarIcon()
626
+ end
606
627
  if success and (result.Success or result.StatusCode == 503) then
607
628
  conn.consecutiveFailures = 0
608
629
  conn.currentRetryDelay = 0.5
@@ -759,7 +780,6 @@ local function activatePlugin(connIndex)
759
780
  conn.isActive = true
760
781
  conn.consecutiveFailures = 0
761
782
  conn.currentRetryDelay = 0.5
762
- ui.screenGui.Enabled = true
763
783
  if idx == State.getActiveTabIndex() then
764
784
  conn.serverUrl = ui.urlInput.Text
765
785
  local portStr = string.match(conn.serverUrl, ":(%d+)$")
@@ -889,15 +909,22 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
889
909
  -- `require(SomeModule)` returns a fresh copy, not the one the running game
890
910
  -- scripts hold. So runtime-mutated module state is invisible to probes.
891
911
  --
892
- -- These bridges fix that by living inside the user's game scripts:
893
- -- - Server: a Script in ServerScriptService that creates a BindableFunction
894
- -- (for our server-peer plugin to invoke directly) plus a RemoteFunction
895
- -- (kept for parity with the upstream primitive's client-callable shape).
912
+ -- These bridges fix that by living inside the user's game scripts. Both
913
+ -- peers use the same symmetric shape:
914
+ -- - Server: a Script in ServerScriptService that creates a BindableFunction.
915
+ -- Plugin (server peer) invokes it with a fresh ModuleScript payload;
916
+ -- require() runs inside the Script VM so it shares the running server's
917
+ -- require cache.
896
918
  -- - Client: a LocalScript in StarterPlayer.StarterPlayerScripts that
897
919
  -- creates a BindableFunction. Plugin invokes it with a fresh ModuleScript
898
920
  -- payload; require() runs inside the LocalScript VM so it shares the
899
921
  -- game's require cache.
900
922
  --
923
+ -- Why ModuleScript+require on both sides (no loadstring): require'd modules
924
+ -- run with the security level they were created at and don't need
925
+ -- ServerScriptService.LoadStringEnabled, so eval_server_runtime works even
926
+ -- when LoadStringEnabled=false (the default in fresh places).
927
+ --
901
928
  -- Lifecycle: TestHandlers.startPlaytest inserts both scripts into the EDIT
902
929
  -- DM right before ExecutePlayModeAsync. ExecutePlayModeAsync clones the
903
930
  -- DataModel into the play DMs, so the scripts come along and run there.
@@ -928,7 +955,6 @@ local CLIENT_SCRIPT_NAME = "__MCP_ClientEvalBridge"
928
955
  local BRIDGE_NAMES = {
929
956
  serverScript = SERVER_SCRIPT_NAME,
930
957
  clientScript = CLIENT_SCRIPT_NAME,
931
- serverRemote = "__MCP_ServerEvalRemote",
932
958
  serverLocal = "__MCP_ServerEvalLocal",
933
959
  clientLocal = "__MCP_ClientEvalBridge",
934
960
  }
@@ -939,7 +965,6 @@ local SERVER_BRIDGE_SOURCE = `\
939
965
  -- stop_playtest. Provides shared-require-cache eval on the server peer for\
940
966
  -- the eval_server_runtime MCP tool.\
941
967
  \
942
- local ReplicatedStorage = game:GetService("ReplicatedStorage")\
943
968
  local ServerScriptService = game:GetService("ServerScriptService")\
944
969
  local RunService = game:GetService("RunService")\
945
970
  \
@@ -947,49 +972,18 @@ if not RunService:IsStudio() then\
947
972
  return\
948
973
  end\
949
974
  \
950
- local function evalCode(source)\
951
- if type(source) ~= "string" then\
952
- return false, "source must be a string"\
953
- end\
954
- local fn, compileErr = loadstring(source, "MCPServerEval")\
955
- if not fn then\
956
- local errStr = tostring(compileErr or "loadstring returned nil")\
957
- -- Roblox returns nil from loadstring when LoadStringEnabled=false.\
958
- -- Surface a clear, actionable error.\
959
- if string.find(errStr, "not enabled", 1, true)\
960
- or string.find(errStr, "disabled", 1, true)\
961
- or errStr == "loadstring returned nil"\
962
- then\
963
- return false,\
964
- "ServerScriptService.LoadStringEnabled is false. eval_server_runtime requires it. "\
965
- .. "Enable it in Studio (ServerScriptService > Properties > LoadStringEnabled = true) "\
966
- .. "and restart the playtest."\
967
- end\
968
- return false, errStr\
969
- end\
970
- return pcall(fn)\
971
- end\
972
- \
973
- -- Defensive cleanup of stale instances from a prior session.\
974
- local prevRf = ReplicatedStorage:FindFirstChild("{BRIDGE_NAMES.serverRemote}")\
975
- if prevRf then prevRf:Destroy() end\
976
975
  local prevBf = ServerScriptService:FindFirstChild("{BRIDGE_NAMES.serverLocal}")\
977
976
  if prevBf then prevBf:Destroy() end\
978
977
  \
979
- local rf = Instance.new("RemoteFunction")\
980
- rf.Name = "{BRIDGE_NAMES.serverRemote}"\
981
- rf.Archivable = false\
982
- rf.Parent = ReplicatedStorage\
983
- rf.OnServerInvoke = function(_player, source)\
984
- return evalCode(source)\
985
- end\
986
- \
987
978
  local bf = Instance.new("BindableFunction")\
988
979
  bf.Name = "{BRIDGE_NAMES.serverLocal}"\
989
980
  bf.Archivable = false\
990
981
  bf.Parent = ServerScriptService\
991
- bf.OnInvoke = function(source)\
992
- return evalCode(source)\
982
+ bf.OnInvoke = function(payload)\
983
+ if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then\
984
+ return false, "payload must be a ModuleScript instance"\
985
+ end\
986
+ return pcall(require, payload)\
993
987
  end\
994
988
  `
995
989
  local CLIENT_BRIDGE_SOURCE = `\
@@ -1086,20 +1080,9 @@ local function installBridges()
1086
1080
  installed = true,
1087
1081
  }
1088
1082
  end
1089
- -- Heuristic check so start_playtest can surface a warning when
1090
- -- LoadStringEnabled is false (eval_server_runtime won't work in that mode).
1091
- -- We can't import the runtime LoadStringEnabled value cleanly without
1092
- -- pulling in the type — read defensively.
1093
- local function loadStringEnabled()
1094
- local ok, value = pcall(function()
1095
- return ServerScriptService.LoadStringEnabled
1096
- end)
1097
- return ok and value == true
1098
- end
1099
1083
  return {
1100
1084
  cleanupBridges = cleanupBridges,
1101
1085
  installBridges = installBridges,
1102
- loadStringEnabled = loadStringEnabled,
1103
1086
  BRIDGE_NAMES = BRIDGE_NAMES,
1104
1087
  }
1105
1088
  ]]></string>
@@ -2815,6 +2798,74 @@ return {
2815
2798
  </Properties>
2816
2799
  </Item>
2817
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">
2818
2869
  <Properties>
2819
2870
  <string name="Name">MetadataHandlers</string>
2820
2871
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3451,7 +3502,7 @@ return {
3451
3502
  ]]></string>
3452
3503
  </Properties>
3453
3504
  </Item>
3454
- <Item class="ModuleScript" referent="13">
3505
+ <Item class="ModuleScript" referent="14">
3455
3506
  <Properties>
3456
3507
  <string name="Name">PropertyHandlers</string>
3457
3508
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3703,7 +3754,7 @@ return {
3703
3754
  ]]></string>
3704
3755
  </Properties>
3705
3756
  </Item>
3706
- <Item class="ModuleScript" referent="14">
3757
+ <Item class="ModuleScript" referent="15">
3707
3758
  <Properties>
3708
3759
  <string name="Name">QueryHandlers</string>
3709
3760
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3811,8 +3862,23 @@ local function searchFiles(requestData)
3811
3862
  }
3812
3863
  end
3813
3864
  local function getPlaceInfo(_requestData)
3865
+ local dataModelName = game.Name
3866
+ local placeName = dataModelName
3867
+ if game.PlaceId > 0 then
3868
+ local MarketplaceService = game:GetService("MarketplaceService")
3869
+ local ok, info = pcall(function()
3870
+ return MarketplaceService:GetProductInfo(game.PlaceId)
3871
+ end)
3872
+ if ok and info ~= nil then
3873
+ local name = info.Name
3874
+ if type(name) == "string" and name ~= "" then
3875
+ placeName = name
3876
+ end
3877
+ end
3878
+ end
3814
3879
  return {
3815
- placeName = game.Name,
3880
+ placeName = placeName,
3881
+ dataModelName = dataModelName,
3816
3882
  placeId = game.PlaceId,
3817
3883
  gameId = game.GameId,
3818
3884
  jobId = game.JobId,
@@ -4730,7 +4796,7 @@ return {
4730
4796
  ]]></string>
4731
4797
  </Properties>
4732
4798
  </Item>
4733
- <Item class="ModuleScript" referent="15">
4799
+ <Item class="ModuleScript" referent="16">
4734
4800
  <Properties>
4735
4801
  <string name="Name">ScriptHandlers</string>
4736
4802
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5426,7 +5492,193 @@ return {
5426
5492
  ]]></string>
5427
5493
  </Properties>
5428
5494
  </Item>
5429
- <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">
5430
5682
  <Properties>
5431
5683
  <string name="Name">TestHandlers</string>
5432
5684
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5437,7 +5689,6 @@ local LogService = _services.LogService
5437
5689
  local _EvalBridges = TS.import(script, script.Parent.Parent, "EvalBridges")
5438
5690
  local installBridges = _EvalBridges.installBridges
5439
5691
  local cleanupBridges = _EvalBridges.cleanupBridges
5440
- local loadStringEnabled = _EvalBridges.loadStringEnabled
5441
5692
  local StudioTestService = game:GetService("StudioTestService")
5442
5693
  local ServerScriptService = game:GetService("ServerScriptService")
5443
5694
  local ScriptEditorService = game:GetService("ScriptEditorService")
@@ -5598,7 +5849,6 @@ local function startPlaytest(requestData)
5598
5849
  -- so eval_server_runtime / eval_client_runtime work without manual setup.
5599
5850
  -- Bridges are cleaned up from the edit DM after the play DMs tear down.
5600
5851
  local bridgeInstall = installBridges()
5601
- local hasLoadString = loadStringEnabled()
5602
5852
  if not bridgeInstall.installed then
5603
5853
  warn(`[MCP] Eval bridge install failed: {bridgeInstall.error}`)
5604
5854
  end
@@ -5632,13 +5882,6 @@ local function startPlaytest(requestData)
5632
5882
  message = msg,
5633
5883
  evalBridges = if bridgeInstall.installed then "installed" else `failed: {bridgeInstall.error}`,
5634
5884
  }
5635
- -- Surface loadstring availability up-front so callers know whether
5636
- -- eval_server_runtime will work before they try it. eval_client_runtime
5637
- -- doesn't need loadstring (it uses ModuleScript+require), so this only
5638
- -- affects the server bridge.
5639
- if not hasLoadString then
5640
- response.serverEvalNote = "ServerScriptService.LoadStringEnabled is false. eval_server_runtime will not work " .. "until you enable it (ServerScriptService > Properties > LoadStringEnabled = true) " .. "and restart the playtest. eval_client_runtime is unaffected."
5641
- end
5642
5885
  return response
5643
5886
  end
5644
5887
  local function stopPlaytest(_requestData)
@@ -5745,7 +5988,7 @@ return {
5745
5988
  </Properties>
5746
5989
  </Item>
5747
5990
  </Item>
5748
- <Item class="ModuleScript" referent="17">
5991
+ <Item class="ModuleScript" referent="19">
5749
5992
  <Properties>
5750
5993
  <string name="Name">Recording</string>
5751
5994
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5775,7 +6018,7 @@ return {
5775
6018
  ]]></string>
5776
6019
  </Properties>
5777
6020
  </Item>
5778
- <Item class="ModuleScript" referent="18">
6021
+ <Item class="ModuleScript" referent="20">
5779
6022
  <Properties>
5780
6023
  <string name="Name">RuntimeLogBuffer</string>
5781
6024
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5956,11 +6199,11 @@ return {
5956
6199
  ]]></string>
5957
6200
  </Properties>
5958
6201
  </Item>
5959
- <Item class="ModuleScript" referent="19">
6202
+ <Item class="ModuleScript" referent="21">
5960
6203
  <Properties>
5961
6204
  <string name="Name">State</string>
5962
6205
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
5963
- local CURRENT_VERSION = "2.10.0"
6206
+ local CURRENT_VERSION = "2.11.0"
5964
6207
  local MAX_CONNECTIONS = 5
5965
6208
  local BASE_PORT = 58741
5966
6209
  local activeTabIndex = 0
@@ -6052,7 +6295,7 @@ return {
6052
6295
  ]]></string>
6053
6296
  </Properties>
6054
6297
  </Item>
6055
- <Item class="ModuleScript" referent="20">
6298
+ <Item class="ModuleScript" referent="22">
6056
6299
  <Properties>
6057
6300
  <string name="Name">UI</string>
6058
6301
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6062,6 +6305,34 @@ local State = TS.import(script, script.Parent, "State")
6062
6305
  local elements = nil
6063
6306
  local pulseAnimation
6064
6307
  local buttonHover = false
6308
+ local toolbarButton
6309
+ local toolbarIcons
6310
+ local lastToolbarIcon
6311
+ local updateToolbarIcon
6312
+ local function setToolbarButton(btn, icons)
6313
+ toolbarButton = btn
6314
+ toolbarIcons = icons
6315
+ lastToolbarIcon = nil
6316
+ updateToolbarIcon()
6317
+ end
6318
+ function updateToolbarIcon()
6319
+ if not toolbarButton or not toolbarIcons then
6320
+ return nil
6321
+ end
6322
+ local conn = State.getActiveConnection()
6323
+ local nextIcon
6324
+ if not conn or not conn.isActive then
6325
+ nextIcon = toolbarIcons.disconnected
6326
+ elseif conn.lastHttpOk and conn.lastMcpOk then
6327
+ nextIcon = toolbarIcons.connected
6328
+ else
6329
+ nextIcon = toolbarIcons.connecting
6330
+ end
6331
+ if nextIcon ~= lastToolbarIcon then
6332
+ toolbarButton.Icon = nextIcon
6333
+ lastToolbarIcon = nextIcon
6334
+ end
6335
+ end
6065
6336
  local tabButtons = {}
6066
6337
  local TWEEN_QUICK = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
6067
6338
  local function tweenProp(instance, props)
@@ -6640,6 +6911,7 @@ local function init(pluginRef)
6640
6911
  refreshTabBar()
6641
6912
  end
6642
6913
  function updateUIState()
6914
+ updateToolbarIcon()
6643
6915
  local conn = State.getActiveConnection()
6644
6916
  if not conn then
6645
6917
  return nil
@@ -6765,6 +7037,8 @@ return {
6765
7037
  updateTabLabel = updateTabLabel,
6766
7038
  stopPulseAnimation = stopPulseAnimation,
6767
7039
  startPulseAnimation = startPulseAnimation,
7040
+ setToolbarButton = setToolbarButton,
7041
+ updateToolbarIcon = updateToolbarIcon,
6768
7042
  getElements = function()
6769
7043
  return elements
6770
7044
  end,
@@ -6772,7 +7046,7 @@ return {
6772
7046
  ]]></string>
6773
7047
  </Properties>
6774
7048
  </Item>
6775
- <Item class="ModuleScript" referent="21">
7049
+ <Item class="ModuleScript" referent="23">
6776
7050
  <Properties>
6777
7051
  <string name="Name">Utils</string>
6778
7052
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7302,11 +7576,11 @@ return {
7302
7576
  </Properties>
7303
7577
  </Item>
7304
7578
  </Item>
7305
- <Item class="Folder" referent="25">
7579
+ <Item class="Folder" referent="27">
7306
7580
  <Properties>
7307
7581
  <string name="Name">include</string>
7308
7582
  </Properties>
7309
- <Item class="ModuleScript" referent="22">
7583
+ <Item class="ModuleScript" referent="24">
7310
7584
  <Properties>
7311
7585
  <string name="Name">Promise</string>
7312
7586
  <string name="Source"><![CDATA[--[[
@@ -9380,7 +9654,7 @@ return Promise
9380
9654
  ]]></string>
9381
9655
  </Properties>
9382
9656
  </Item>
9383
- <Item class="ModuleScript" referent="23">
9657
+ <Item class="ModuleScript" referent="25">
9384
9658
  <Properties>
9385
9659
  <string name="Name">RuntimeLib</string>
9386
9660
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -9647,15 +9921,15 @@ return TS
9647
9921
  </Properties>
9648
9922
  </Item>
9649
9923
  </Item>
9650
- <Item class="Folder" referent="26">
9924
+ <Item class="Folder" referent="28">
9651
9925
  <Properties>
9652
9926
  <string name="Name">node_modules</string>
9653
9927
  </Properties>
9654
- <Item class="Folder" referent="27">
9928
+ <Item class="Folder" referent="29">
9655
9929
  <Properties>
9656
9930
  <string name="Name">@rbxts</string>
9657
9931
  </Properties>
9658
- <Item class="ModuleScript" referent="24">
9932
+ <Item class="ModuleScript" referent="26">
9659
9933
  <Properties>
9660
9934
  <string name="Name">services</string>
9661
9935
  <string name="Source"><![CDATA[return setmetatable({}, {