rbxstudio-mcp 1.14.3 → 2.1.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.
@@ -111,7 +111,7 @@ local screenGui = plugin:CreateDockWidgetPluginGuiAsync(
111
111
  "MCPServerInterface",
112
112
  DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, false, 400, 500, 350, 450)
113
113
  )
114
- screenGui.Title = "MCP Server v1.14.0"
114
+ screenGui.Title = "MCP Server v2.0.0"
115
115
 
116
116
  local mainFrame = Instance.new("Frame")
117
117
  mainFrame.Size = UDim2.new(1, 0, 1, 0)
@@ -164,7 +164,7 @@ local versionLabel = Instance.new("TextLabel")
164
164
  versionLabel.Size = UDim2.new(1, 0, 0, 16)
165
165
  versionLabel.Position = UDim2.new(0, 0, 0, 32)
166
166
  versionLabel.BackgroundTransparency = 1
167
- versionLabel.Text = "AI Integration • v1.14.0"
167
+ versionLabel.Text = "AI Integration • v2.0.0"
168
168
  versionLabel.TextColor3 = Color3.fromRGB(191, 219, 254)
169
169
  versionLabel.TextScaled = false
170
170
  versionLabel.TextSize = 12
@@ -4015,6 +4015,223 @@ handlers.redo = function(requestData)
4015
4015
  end
4016
4016
  end
4017
4017
 
4018
+ -- ============================================
4019
+ -- EXECUTE LUA HANDLER (Run arbitrary Lua code)
4020
+ -- ============================================
4021
+
4022
+ handlers.executeLua = function(requestData)
4023
+ local code = requestData.code
4024
+
4025
+ if not code or type(code) ~= "string" then
4026
+ return {
4027
+ success = false,
4028
+ error = "code parameter is required and must be a string"
4029
+ }
4030
+ end
4031
+
4032
+ -- Create a sandboxed environment with access to game services and Roblox types
4033
+ local env = setmetatable({
4034
+ -- Core globals
4035
+ game = game,
4036
+ workspace = workspace,
4037
+ script = nil, -- No script context
4038
+
4039
+ -- Services (commonly used)
4040
+ Players = game:GetService("Players"),
4041
+ Workspace = workspace,
4042
+ ReplicatedStorage = game:GetService("ReplicatedStorage"),
4043
+ ServerStorage = game:GetService("ServerStorage"),
4044
+ ServerScriptService = game:GetService("ServerScriptService"),
4045
+ StarterGui = game:GetService("StarterGui"),
4046
+ StarterPack = game:GetService("StarterPack"),
4047
+ StarterPlayer = game:GetService("StarterPlayer"),
4048
+ Lighting = game:GetService("Lighting"),
4049
+ SoundService = game:GetService("SoundService"),
4050
+ TweenService = game:GetService("TweenService"),
4051
+ RunService = game:GetService("RunService"),
4052
+ UserInputService = game:GetService("UserInputService"),
4053
+ HttpService = game:GetService("HttpService"),
4054
+ CollectionService = game:GetService("CollectionService"),
4055
+ PhysicsService = game:GetService("PhysicsService"),
4056
+ PathfindingService = game:GetService("PathfindingService"),
4057
+ TextService = game:GetService("TextService"),
4058
+ MarketplaceService = game:GetService("MarketplaceService"),
4059
+ TeleportService = game:GetService("TeleportService"),
4060
+ GroupService = game:GetService("GroupService"),
4061
+ BadgeService = game:GetService("BadgeService"),
4062
+ DataStoreService = game:GetService("DataStoreService"),
4063
+ MemoryStoreService = game:GetService("MemoryStoreService"),
4064
+ MessagingService = game:GetService("MessagingService"),
4065
+ PolicyService = game:GetService("PolicyService"),
4066
+ LocalizationService = game:GetService("LocalizationService"),
4067
+ Selection = game:GetService("Selection"),
4068
+ ChangeHistoryService = game:GetService("ChangeHistoryService"),
4069
+ ScriptEditorService = game:GetService("ScriptEditorService"),
4070
+
4071
+ -- Roblox constructors
4072
+ Instance = Instance,
4073
+ Vector3 = Vector3,
4074
+ Vector2 = Vector2,
4075
+ CFrame = CFrame,
4076
+ Color3 = Color3,
4077
+ BrickColor = BrickColor,
4078
+ UDim = UDim,
4079
+ UDim2 = UDim2,
4080
+ Rect = Rect,
4081
+ Ray = Ray,
4082
+ Region3 = Region3,
4083
+ NumberSequence = NumberSequence,
4084
+ NumberSequenceKeypoint = NumberSequenceKeypoint,
4085
+ ColorSequence = ColorSequence,
4086
+ ColorSequenceKeypoint = ColorSequenceKeypoint,
4087
+ NumberRange = NumberRange,
4088
+ TweenInfo = TweenInfo,
4089
+ Font = Font,
4090
+
4091
+ -- Enums
4092
+ Enum = Enum,
4093
+
4094
+ -- Lua globals
4095
+ print = print,
4096
+ warn = warn,
4097
+ error = error,
4098
+ assert = assert,
4099
+ type = type,
4100
+ typeof = typeof,
4101
+ tostring = tostring,
4102
+ tonumber = tonumber,
4103
+ pairs = pairs,
4104
+ ipairs = ipairs,
4105
+ next = next,
4106
+ select = select,
4107
+ unpack = unpack,
4108
+ pcall = pcall,
4109
+ xpcall = xpcall,
4110
+ rawget = rawget,
4111
+ rawset = rawset,
4112
+ rawequal = rawequal,
4113
+ setmetatable = setmetatable,
4114
+ getmetatable = getmetatable,
4115
+ newproxy = newproxy,
4116
+
4117
+ -- Tables
4118
+ table = table,
4119
+ string = string,
4120
+ math = math,
4121
+ os = { time = os.time, date = os.date, difftime = os.difftime, clock = os.clock },
4122
+ coroutine = coroutine,
4123
+ bit32 = bit32,
4124
+ utf8 = utf8,
4125
+ buffer = buffer,
4126
+
4127
+ -- Task library
4128
+ task = task,
4129
+ wait = task.wait,
4130
+ delay = task.delay,
4131
+ spawn = task.spawn,
4132
+ defer = task.defer,
4133
+
4134
+ -- Instance helper from plugin
4135
+ getInstanceByPath = getInstanceByPath,
4136
+
4137
+ -- Allow loadstring for dynamic code (we're already in a plugin context)
4138
+ loadstring = loadstring,
4139
+ }, { __index = _G })
4140
+
4141
+ local success, compileResult = pcall(function()
4142
+ return loadstring(code)
4143
+ end)
4144
+
4145
+ if not success then
4146
+ return {
4147
+ success = false,
4148
+ error = "Failed to compile code: " .. tostring(compileResult)
4149
+ }
4150
+ end
4151
+
4152
+ local fn = compileResult
4153
+ if not fn then
4154
+ return {
4155
+ success = false,
4156
+ error = "Failed to compile code (loadstring returned nil)"
4157
+ }
4158
+ end
4159
+
4160
+ -- Set the environment
4161
+ setfenv(fn, env)
4162
+
4163
+ -- Execute the code
4164
+ local execSuccess, execResult = pcall(fn)
4165
+
4166
+ if not execSuccess then
4167
+ return {
4168
+ success = false,
4169
+ error = "Runtime error: " .. tostring(execResult)
4170
+ }
4171
+ end
4172
+
4173
+ -- Serialize the result
4174
+ local function serializeResult(value, depth)
4175
+ depth = depth or 0
4176
+ if depth > 5 then return "<max depth>" end
4177
+
4178
+ local t = typeof(value)
4179
+
4180
+ if value == nil then
4181
+ return nil
4182
+ elseif t == "string" then
4183
+ return value
4184
+ elseif t == "number" or t == "boolean" then
4185
+ return value
4186
+ elseif t == "Vector3" then
4187
+ return { type = "Vector3", X = value.X, Y = value.Y, Z = value.Z }
4188
+ elseif t == "Vector2" then
4189
+ return { type = "Vector2", X = value.X, Y = value.Y }
4190
+ elseif t == "CFrame" then
4191
+ local components = { value:GetComponents() }
4192
+ return { type = "CFrame", components = components }
4193
+ elseif t == "Color3" then
4194
+ return { type = "Color3", R = value.R, G = value.G, B = value.B }
4195
+ elseif t == "BrickColor" then
4196
+ return { type = "BrickColor", Name = value.Name }
4197
+ elseif t == "UDim2" then
4198
+ return { type = "UDim2", X = { Scale = value.X.Scale, Offset = value.X.Offset }, Y = { Scale = value.Y.Scale, Offset = value.Y.Offset } }
4199
+ elseif t == "UDim" then
4200
+ return { type = "UDim", Scale = value.Scale, Offset = value.Offset }
4201
+ elseif t == "Instance" then
4202
+ return { type = "Instance", ClassName = value.ClassName, Name = value.Name, Path = value:GetFullName() }
4203
+ elseif t == "EnumItem" then
4204
+ return { type = "EnumItem", Name = tostring(value) }
4205
+ elseif t == "table" then
4206
+ local result = {}
4207
+ local count = 0
4208
+ for k, v in pairs(value) do
4209
+ count = count + 1
4210
+ if count > 100 then
4211
+ result["__truncated__"] = true
4212
+ break
4213
+ end
4214
+ local keyStr = type(k) == "string" and k or tostring(k)
4215
+ result[keyStr] = serializeResult(v, depth + 1)
4216
+ end
4217
+ return result
4218
+ elseif t == "function" then
4219
+ return "<function>"
4220
+ elseif t == "thread" then
4221
+ return "<thread>"
4222
+ else
4223
+ return "<" .. t .. ">"
4224
+ end
4225
+ end
4226
+
4227
+ return {
4228
+ success = true,
4229
+ result = serializeResult(execResult),
4230
+ resultType = typeof(execResult),
4231
+ message = "Code executed successfully"
4232
+ }
4233
+ end
4234
+
4018
4235
  -- ============================================
4019
4236
  -- INSERT ASSET HANDLER (Creator Store)
4020
4237
  -- ============================================
@@ -4382,6 +4599,295 @@ handlers.captureScreenshot = function(requestData)
4382
4599
  return resultData
4383
4600
  end
4384
4601
 
4602
+ -- ============================================
4603
+ -- VIEWPORTFRAME RENDERING SYSTEM
4604
+ -- ============================================
4605
+
4606
+ -- Camera angle presets
4607
+ local CameraPresets = {
4608
+ -- Standard orthographic views
4609
+ front = CFrame.new(0, 0, 10) * CFrame.Angles(0, 0, 0),
4610
+ back = CFrame.new(0, 0, -10) * CFrame.Angles(0, math.pi, 0),
4611
+ left = CFrame.new(-10, 0, 0) * CFrame.Angles(0, math.pi/2, 0),
4612
+ right = CFrame.new(10, 0, 0) * CFrame.Angles(0, -math.pi/2, 0),
4613
+ top = CFrame.new(0, 10, 0) * CFrame.Angles(-math.pi/2, 0, 0),
4614
+ bottom = CFrame.new(0, -10, 0) * CFrame.Angles(math.pi/2, 0, 0),
4615
+
4616
+ -- Isometric views (popular for thumbnails)
4617
+ iso = CFrame.new(7, 7, 7) * CFrame.Angles(-math.pi/6, math.pi/4, 0),
4618
+ iso_front = CFrame.new(5, 5, 10) * CFrame.Angles(-math.pi/6, 0, 0),
4619
+ iso_back = CFrame.new(-5, 5, -10) * CFrame.Angles(-math.pi/6, math.pi, 0),
4620
+
4621
+ -- Dramatic angles
4622
+ low_angle = CFrame.new(0, -5, 10) * CFrame.Angles(math.pi/6, 0, 0),
4623
+ high_angle = CFrame.new(0, 15, 8) * CFrame.Angles(-math.pi/3, 0, 0),
4624
+ }
4625
+
4626
+ -- Apply lighting preset to WorldModel
4627
+ local function applyLightingPreset(worldModel, preset)
4628
+ -- Remove existing lighting
4629
+ for _, child in ipairs(worldModel:GetChildren()) do
4630
+ if child:IsA("Light") or child:IsA("Sky") or child:IsA("Atmosphere") then
4631
+ child:Destroy()
4632
+ end
4633
+ end
4634
+
4635
+ if preset == "bright" or preset == "showcase" then
4636
+ -- Three-point lighting setup
4637
+ local keyLight = Instance.new("PointLight")
4638
+ keyLight.Brightness = 2
4639
+ keyLight.Range = 60
4640
+ keyLight.Color = Color3.fromRGB(255, 255, 255)
4641
+ keyLight.Parent = worldModel
4642
+
4643
+ local fillLight = Instance.new("PointLight")
4644
+ fillLight.Brightness = 1
4645
+ fillLight.Range = 40
4646
+ fillLight.Color = Color3.fromRGB(200, 220, 255)
4647
+ fillLight.Parent = worldModel
4648
+
4649
+ local rimLight = Instance.new("PointLight")
4650
+ rimLight.Brightness = 1.5
4651
+ rimLight.Range = 50
4652
+ rimLight.Color = Color3.fromRGB(255, 240, 200)
4653
+ rimLight.Parent = worldModel
4654
+
4655
+ elseif preset == "studio" or preset == "flat" then
4656
+ -- Flat, even lighting like in Studio
4657
+ local ambientLight = Instance.new("PointLight")
4658
+ ambientLight.Brightness = 1
4659
+ ambientLight.Range = 100
4660
+ ambientLight.Color = Color3.fromRGB(255, 255, 255)
4661
+ ambientLight.Parent = worldModel
4662
+
4663
+ elseif preset == "dark" or preset == "dramatic" then
4664
+ -- Single directional light
4665
+ local light = Instance.new("PointLight")
4666
+ light.Brightness = 1.5
4667
+ light.Range = 40
4668
+ light.Color = Color3.fromRGB(255, 200, 150)
4669
+ light.Parent = worldModel
4670
+ end
4671
+ -- default: no lights added (ambient only)
4672
+ end
4673
+
4674
+ -- Calculate bounding box of a model
4675
+ local function getModelBoundingBox(model)
4676
+ local parts = {}
4677
+
4678
+ -- Collect all BaseParts
4679
+ for _, child in ipairs(model:GetDescendants()) do
4680
+ if child:IsA("BasePart") then
4681
+ table.insert(parts, child)
4682
+ end
4683
+ end
4684
+
4685
+ if #parts == 0 then
4686
+ -- No parts found, return default
4687
+ return Vector3.new(1, 1, 1), Vector3.new(0, 0, 0)
4688
+ end
4689
+
4690
+ -- Calculate bounding box
4691
+ local minPos = parts[1].Position - parts[1].Size/2
4692
+ local maxPos = parts[1].Position + parts[1].Size/2
4693
+
4694
+ for _, part in ipairs(parts) do
4695
+ local partMin = part.Position - part.Size/2
4696
+ local partMax = part.Position + part.Size/2
4697
+
4698
+ minPos = Vector3.new(
4699
+ math.min(minPos.X, partMin.X),
4700
+ math.min(minPos.Y, partMin.Y),
4701
+ math.min(minPos.Z, partMin.Z)
4702
+ )
4703
+ maxPos = Vector3.new(
4704
+ math.max(maxPos.X, partMax.X),
4705
+ math.max(maxPos.Y, partMax.Y),
4706
+ math.max(maxPos.Z, partMax.Z)
4707
+ )
4708
+ end
4709
+
4710
+ local size = maxPos - minPos
4711
+ local center = (minPos + maxPos) / 2
4712
+
4713
+ return size, center
4714
+ end
4715
+
4716
+ -- Main handler: Render Object View
4717
+ handlers.renderObjectView = function(requestData)
4718
+ local success, result = pcall(function()
4719
+ local instancePath = requestData.instancePath
4720
+ if not instancePath then
4721
+ return {
4722
+ success = false,
4723
+ error = "instancePath is required"
4724
+ }
4725
+ end
4726
+
4727
+ -- Parse resolution
4728
+ local resolution = requestData.resolution or {width = 512, height = 512}
4729
+ local width = resolution.width or 512
4730
+ local height = resolution.height or 512
4731
+
4732
+ -- Clamp resolution for performance
4733
+ width = math.clamp(width, 64, 2048)
4734
+ height = math.clamp(height, 64, 2048)
4735
+
4736
+ local anglePreset = requestData.angle or "iso"
4737
+ local lighting = requestData.lighting or "bright"
4738
+ local background = requestData.background or "transparent"
4739
+ local autoDistance = requestData.autoDistance ~= false
4740
+
4741
+ -- Get target instance
4742
+ local targetInstance = getInstanceByPath(instancePath)
4743
+ if not targetInstance then
4744
+ return {
4745
+ success = false,
4746
+ error = "Instance not found: " .. instancePath
4747
+ }
4748
+ end
4749
+
4750
+ -- Create ViewportFrame (no parent = no GUI overhead)
4751
+ local viewportFrame = Instance.new("ViewportFrame")
4752
+ viewportFrame.Size = UDim2.fromOffset(width, height)
4753
+ viewportFrame.BackgroundTransparency = background == "transparent" and 1 or 0
4754
+ viewportFrame.BackgroundColor3 = background == "grid" and Color3.fromRGB(128, 128, 128) or Color3.fromRGB(255, 255, 255)
4755
+
4756
+ -- Create WorldModel for viewport
4757
+ local worldModel = Instance.new("WorldModel")
4758
+ worldModel.Parent = viewportFrame
4759
+
4760
+ local camera = Instance.new("Camera")
4761
+ camera.Parent = viewportFrame
4762
+ viewportFrame.CurrentCamera = camera
4763
+
4764
+ -- Clone target into WorldModel
4765
+ local clonedInstance = targetInstance:Clone()
4766
+ clonedInstance.Parent = worldModel
4767
+
4768
+ -- Calculate bounding box and center
4769
+ local boundingSize, boundingCenter = getModelBoundingBox(clonedInstance)
4770
+ local maxDimension = math.max(boundingSize.X, boundingSize.Y, boundingSize.Z)
4771
+
4772
+ -- Position camera
4773
+ local cameraOffset
4774
+ if type(anglePreset) == "string" and CameraPresets[anglePreset] then
4775
+ cameraOffset = CameraPresets[anglePreset]
4776
+ elseif type(anglePreset) == "table" then
4777
+ -- Custom angle {pitch, yaw, roll, distance}
4778
+ local pitch = math.rad(anglePreset.pitch or 0)
4779
+ local yaw = math.rad(anglePreset.yaw or 0)
4780
+ local roll = math.rad(anglePreset.roll or 0)
4781
+ local distance = anglePreset.distance or 10
4782
+ cameraOffset = CFrame.new(0, 0, distance) * CFrame.Angles(pitch, yaw, roll)
4783
+ else
4784
+ cameraOffset = CameraPresets.iso
4785
+ end
4786
+
4787
+ -- Auto-calculate distance to fit object
4788
+ local cameraDistance = 10
4789
+ if autoDistance then
4790
+ local fov = 70
4791
+ cameraDistance = maxDimension / (2 * math.tan(math.rad(fov / 2))) * 1.3
4792
+ cameraDistance = math.max(cameraDistance, 1) -- Minimum distance
4793
+ elseif type(anglePreset) == "table" and anglePreset.distance then
4794
+ cameraDistance = anglePreset.distance
4795
+ end
4796
+
4797
+ -- Apply distance to camera offset
4798
+ local offsetDirection = cameraOffset.Position.Unit
4799
+ local finalCameraPos = boundingCenter + (offsetDirection * cameraDistance)
4800
+
4801
+ camera.CFrame = CFrame.new(finalCameraPos, boundingCenter) * cameraOffset.Rotation
4802
+ camera.FieldOfView = 70
4803
+
4804
+ -- Apply lighting
4805
+ applyLightingPreset(worldModel, lighting)
4806
+
4807
+ -- Add grid background if requested
4808
+ if background == "grid" then
4809
+ local gridPart = Instance.new("Part")
4810
+ gridPart.Size = Vector3.new(maxDimension * 5, 0.1, maxDimension * 5)
4811
+ gridPart.Position = boundingCenter - Vector3.new(0, boundingSize.Y/2 + 0.1, 0)
4812
+ gridPart.Anchored = true
4813
+ gridPart.Material = Enum.Material.SmoothPlastic
4814
+ gridPart.Color = Color3.fromRGB(200, 200, 200)
4815
+ gridPart.Parent = worldModel
4816
+ end
4817
+
4818
+ -- Wait for viewport to render
4819
+ task.wait(0.1)
4820
+ RunService.Heartbeat:Wait()
4821
+
4822
+ -- Capture to EditableImage using AssetService
4823
+ local AssetService = game:GetService("AssetService")
4824
+ local editableImage = AssetService:CreateEditableImage({
4825
+ Size = Vector2.new(width, height)
4826
+ })
4827
+
4828
+ -- Read pixels from viewport
4829
+ local pixelSuccess, pixelBuffer = pcall(function()
4830
+ return viewportFrame:ReadPixelsBuffer(Vector2.new(0, 0), Vector2.new(width, height))
4831
+ end)
4832
+
4833
+ if not pixelSuccess or not pixelBuffer then
4834
+ viewportFrame:Destroy()
4835
+ editableImage:Destroy()
4836
+ return {
4837
+ success = false,
4838
+ error = "Failed to capture viewport pixels: " .. tostring(pixelBuffer)
4839
+ }
4840
+ end
4841
+
4842
+ -- Write pixels to EditableImage
4843
+ editableImage:WritePixelsBuffer(Vector2.new(0, 0), Vector2.new(width, height), pixelBuffer)
4844
+
4845
+ -- Convert to base64 RGBA
4846
+ local rgbaBuffer = editableImage:ReadPixelsBuffer(Vector2.new(0, 0), editableImage.Size)
4847
+ local rgbaString = buffer.tostring(rgbaBuffer)
4848
+ local base64Data = base64encode(rgbaString)
4849
+
4850
+ -- Cleanup
4851
+ viewportFrame:Destroy()
4852
+ editableImage:Destroy()
4853
+
4854
+ return {
4855
+ success = true,
4856
+ base64 = base64Data,
4857
+ width = width,
4858
+ height = height,
4859
+ viewInfo = {
4860
+ objectName = targetInstance.Name,
4861
+ objectClass = targetInstance.ClassName,
4862
+ boundingBox = {
4863
+ size = {x = boundingSize.X, y = boundingSize.Y, z = boundingSize.Z},
4864
+ center = {x = boundingCenter.X, y = boundingCenter.Y, z = boundingCenter.Z}
4865
+ },
4866
+ camera = {
4867
+ distance = cameraDistance,
4868
+ position = {x = finalCameraPos.X, y = finalCameraPos.Y, z = finalCameraPos.Z}
4869
+ },
4870
+ settings = {
4871
+ angle = anglePreset,
4872
+ lighting = lighting,
4873
+ background = background,
4874
+ resolution = {width = width, height = height}
4875
+ }
4876
+ },
4877
+ message = "Rendered " .. targetInstance.Name .. " at " .. width .. "x" .. height
4878
+ }
4879
+ end)
4880
+
4881
+ if success then
4882
+ return result
4883
+ else
4884
+ return {
4885
+ success = false,
4886
+ error = "Failed to render object: " .. tostring(result)
4887
+ }
4888
+ end
4889
+ end
4890
+
4385
4891
  -- Now that all handlers are defined, populate the endpoint mapping table
4386
4892
  endpointHandlers = {
4387
4893
  ["/api/file-tree"] = handlers.getFileTree,
@@ -4431,11 +4937,15 @@ endpointHandlers = {
4431
4937
  ["/api/insert-asset"] = handlers.insertAsset,
4432
4938
  ["/api/undo"] = handlers.undo,
4433
4939
  ["/api/redo"] = handlers.redo,
4940
+ -- Execute Lua
4941
+ ["/api/execute-lua"] = handlers.executeLua,
4434
4942
  -- Playtest control
4435
4943
  ["/api/play-solo"] = handlers.playSolo,
4436
4944
  ["/api/stop-play"] = handlers.stopPlay,
4437
4945
  -- Screenshot
4438
4946
  ["/api/capture-screenshot"] = handlers.captureScreenshot,
4947
+ -- ViewportFrame rendering
4948
+ ["/api/render-object-view"] = handlers.renderObjectView,
4439
4949
  }
4440
4950
 
4441
4951
  local function updateUIState()