@chrrxs/robloxstudio-mcp 2.16.4 → 2.17.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.
@@ -16,6 +16,7 @@ local cleanupLegacyEditBridges = _EvalBridges.cleanupLegacyEditBridges
16
16
  local ensureRuntimeBridgeInstalled = _EvalBridges.ensureRuntimeBridgeInstalled
17
17
  local RuntimeLogBuffer = TS.import(script, script, "modules", "RuntimeLogBuffer")
18
18
  local StopPlayMonitor = TS.import(script, script, "modules", "StopPlayMonitor")
19
+ local BreakpointHandlers = TS.import(script, script, "modules", "handlers", "BreakpointHandlers")
19
20
  local RenderMonitor = TS.import(script, script, "modules", "RenderMonitor")
20
21
  -- Track render-loop liveness so input/screenshot tools can report "window
21
22
  -- minimized / not rendering" instead of silently no-op'ing. No-op in the
@@ -29,6 +30,7 @@ RuntimeLogBuffer.install()
29
30
  -- edit DM (write the flag) and the play-server DM (read+act on the flag) can
30
31
  -- access plugin:SetSetting/GetSetting.
31
32
  StopPlayMonitor.init(plugin)
33
+ BreakpointHandlers.init(plugin)
32
34
  ServerUrlSettings.init(plugin)
33
35
  UI.init(plugin)
34
36
  local elements = UI.getElements()
@@ -147,6 +149,8 @@ local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "Scen
147
149
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
148
150
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
149
151
  local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
152
+ local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
153
+ local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
150
154
  local LuauExec = TS.import(script, script.Parent, "LuauExec")
151
155
  local State = TS.import(script, script.Parent, "State")
152
156
  local HttpDiagnostics = TS.import(script, script.Parent, "HttpDiagnostics")
@@ -215,6 +219,8 @@ local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
215
219
  ["/api/get-runtime-logs"] = true,
216
220
  ["/api/get-memory-breakdown"] = true,
217
221
  ["/api/get-scene-analysis"] = true,
222
+ ["/api/breakpoints"] = true,
223
+ ["/api/capture-script-profiler"] = true,
218
224
  ["/api/multiplayer-test-state"] = true,
219
225
  ["/api/multiplayer-test-leave-client"] = true,
220
226
  ["/api/capture-begin"] = true,
@@ -404,6 +410,12 @@ local function setupClientBroker()
404
410
  if payload and payload.endpoint == "/api/get-scene-analysis" then
405
411
  return SceneAnalysisHandlers.getSceneAnalysis(payload.data or {})
406
412
  end
413
+ if payload and payload.endpoint == "/api/breakpoints" then
414
+ return BreakpointHandlers.breakpoints(payload.data or {})
415
+ end
416
+ if payload and payload.endpoint == "/api/capture-script-profiler" then
417
+ return ScriptProfilerHandlers.captureScriptProfiler(payload.data or {})
418
+ end
407
419
  if payload and payload.endpoint == "/api/multiplayer-test-state" then
408
420
  return handleMultiplayerTestState()
409
421
  end
@@ -650,6 +662,8 @@ local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
650
662
  local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
651
663
  local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
652
664
  local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
665
+ local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
666
+ local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
653
667
  local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
654
668
  local ClientBroker = TS.import(script, script.Parent, "ClientBroker")
655
669
  local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
@@ -739,7 +753,6 @@ local routeMap = {
739
753
  ["/api/grep-scripts"] = QueryHandlers.grepScripts,
740
754
  ["/api/get-descendants"] = QueryHandlers.getDescendants,
741
755
  ["/api/compare-instances"] = QueryHandlers.compareInstances,
742
- ["/api/get-output-log"] = QueryHandlers.getOutputLog,
743
756
  ["/api/set-property"] = PropertyHandlers.setProperty,
744
757
  ["/api/set-properties"] = PropertyHandlers.setProperties,
745
758
  ["/api/mass-set-property"] = PropertyHandlers.massSetProperty,
@@ -771,7 +784,6 @@ local routeMap = {
771
784
  ["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
772
785
  ["/api/start-playtest"] = TestHandlers.startPlaytest,
773
786
  ["/api/stop-playtest"] = TestHandlers.stopPlaytest,
774
- ["/api/get-playtest-output"] = TestHandlers.getPlaytestOutput,
775
787
  ["/api/multiplayer-test-start"] = TestHandlers.multiplayerTestStart,
776
788
  ["/api/multiplayer-test-state"] = TestHandlers.multiplayerTestState,
777
789
  ["/api/multiplayer-test-add-players"] = TestHandlers.multiplayerTestAddPlayers,
@@ -791,6 +803,8 @@ local routeMap = {
791
803
  ["/api/simulate-keyboard-input"] = InputHandlers.simulateKeyboardInput,
792
804
  ["/api/find-and-replace-in-scripts"] = ScriptHandlers.findAndReplaceInScripts,
793
805
  ["/api/get-runtime-logs"] = LogHandlers.getRuntimeLogs,
806
+ ["/api/breakpoints"] = BreakpointHandlers.breakpoints,
807
+ ["/api/capture-script-profiler"] = ScriptProfilerHandlers.captureScriptProfiler,
794
808
  ["/api/export-rbxm"] = SerializationHandlers.exportRbxm,
795
809
  ["/api/import-rbxm"] = SerializationHandlers.importRbxm,
796
810
  ["/api/get-memory-breakdown"] = MemoryHandlers.getMemoryBreakdown,
@@ -1411,9 +1425,9 @@ local function computeBridgeStamp()
1411
1425
  for i = 1, #combined do
1412
1426
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1413
1427
  end
1414
- -- "2.16.4" is replaced with the package version at package time
1428
+ -- "2.17.0" is replaced with the package version at package time
1415
1429
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1416
- return `{tostring(h)}-2.16.4`
1430
+ return `{tostring(h)}-2.17.0`
1417
1431
  end
1418
1432
  local BRIDGE_STAMP = computeBridgeStamp()
1419
1433
  local function setSource(scriptInst, source)
@@ -1857,6 +1871,524 @@ return {
1857
1871
  </Properties>
1858
1872
  </Item>
1859
1873
  <Item class="ModuleScript" referent="7">
1874
+ <Properties>
1875
+ <string name="Name">BreakpointHandlers</string>
1876
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
1877
+ local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
1878
+ local Utils = TS.import(script, script.Parent.Parent, "Utils")
1879
+ local _binding = Utils
1880
+ local getInstanceByPath = _binding.getInstanceByPath
1881
+ local HttpService = game:GetService("HttpService")
1882
+ local RunService = game:GetService("RunService")
1883
+ local ServerStorage = game:GetService("ServerStorage")
1884
+ local LOG_PREFIX = "Breakpoint"
1885
+ local REGISTRY_KEY_PREFIX = "MCP_BREAKPOINTS_V1_"
1886
+ local MCP_PLACE_ID_ATTRIBUTE = "__MCPPlaceId"
1887
+ local pluginRef
1888
+ local loadedRegistryKey
1889
+ local loadedRegistryFromSettings = false
1890
+ local breakpoints = {}
1891
+ local function init(p)
1892
+ pluginRef = p
1893
+ end
1894
+ local function breakpointKey(scriptPath, line)
1895
+ return `{scriptPath}:{line}`
1896
+ end
1897
+ local function computeInstanceId()
1898
+ if game.PlaceId ~= 0 then
1899
+ return `place:{tostring(game.PlaceId)}`
1900
+ end
1901
+ local existing = ServerStorage:GetAttribute(MCP_PLACE_ID_ATTRIBUTE)
1902
+ if type(existing) == "string" and existing ~= "" then
1903
+ return `anon:{existing}`
1904
+ end
1905
+ local fresh = HttpService:GenerateGUID(false)
1906
+ pcall(function()
1907
+ return ServerStorage:SetAttribute(MCP_PLACE_ID_ATTRIBUTE, fresh)
1908
+ end)
1909
+ return `anon:{fresh}`
1910
+ end
1911
+ local function detectRole()
1912
+ if not RunService:IsRunning() then
1913
+ return "edit"
1914
+ end
1915
+ if RunService:IsServer() then
1916
+ return "server"
1917
+ end
1918
+ return "client"
1919
+ end
1920
+ local function requestedRole(requestData)
1921
+ local ___mcp_target_role = requestData.__mcp_target_role
1922
+ local _condition = type(___mcp_target_role) == "string"
1923
+ if _condition then
1924
+ _condition = requestData.__mcp_target_role ~= ""
1925
+ end
1926
+ return if _condition then requestData.__mcp_target_role else detectRole()
1927
+ end
1928
+ local function registryScope(requestData)
1929
+ local ___mcp_instance_id = requestData.__mcp_instance_id
1930
+ local _condition = type(___mcp_instance_id) == "string"
1931
+ if _condition then
1932
+ _condition = requestData.__mcp_instance_id ~= ""
1933
+ end
1934
+ local instanceId = if _condition then requestData.__mcp_instance_id else computeInstanceId()
1935
+ local role = requestedRole(requestData)
1936
+ return {
1937
+ key = `{REGISTRY_KEY_PREFIX}{instanceId}:{role}`,
1938
+ }
1939
+ end
1940
+ local function readSetting(key)
1941
+ if not pluginRef then
1942
+ return nil
1943
+ end
1944
+ local ok, value = pcall(function()
1945
+ return pluginRef:GetSetting(key)
1946
+ end)
1947
+ return if ok then value else nil
1948
+ end
1949
+ local function writeSetting(key, value)
1950
+ if not pluginRef then
1951
+ return false
1952
+ end
1953
+ local ok = pcall(function()
1954
+ return pluginRef:SetSetting(key, value)
1955
+ end)
1956
+ return ok
1957
+ end
1958
+ local function decodePersistedBreakpointEntry(value)
1959
+ local _value = value
1960
+ if not (type(_value) == "table") then
1961
+ return nil
1962
+ end
1963
+ local data = value
1964
+ local _script_path = data.script_path
1965
+ local _condition = not (type(_script_path) == "string")
1966
+ if not _condition then
1967
+ _condition = data.script_path == ""
1968
+ end
1969
+ if _condition then
1970
+ return nil
1971
+ end
1972
+ local _line = data.line
1973
+ local _condition_1 = not (type(_line) == "number")
1974
+ if not _condition_1 then
1975
+ _condition_1 = data.line < 1
1976
+ end
1977
+ if _condition_1 then
1978
+ return nil
1979
+ end
1980
+ return {
1981
+ script_path = data.script_path,
1982
+ line = math.floor(data.line),
1983
+ }
1984
+ end
1985
+ local function loadRegistry(requestData)
1986
+ local scope = registryScope(requestData)
1987
+ if loadedRegistryKey ~= scope.key then
1988
+ table.clear(breakpoints)
1989
+ loadedRegistryKey = scope.key
1990
+ loadedRegistryFromSettings = false
1991
+ end
1992
+ if loadedRegistryFromSettings then
1993
+ return scope
1994
+ end
1995
+ loadedRegistryFromSettings = true
1996
+ local stored = readSetting(scope.key)
1997
+ if stored == nil then
1998
+ return scope
1999
+ end
2000
+ local decoded = stored
2001
+ if type(stored) == "string" then
2002
+ local ok, result = pcall(function()
2003
+ return HttpService:JSONDecode(stored)
2004
+ end)
2005
+ if not ok then
2006
+ return scope
2007
+ end
2008
+ decoded = result
2009
+ end
2010
+ local _decoded = decoded
2011
+ if not (type(_decoded) == "table") then
2012
+ return scope
2013
+ end
2014
+ table.clear(breakpoints)
2015
+ for _, item in decoded do
2016
+ local entry = decodePersistedBreakpointEntry(item)
2017
+ if entry then
2018
+ local _arg0 = breakpointKey(entry.script_path, entry.line)
2019
+ breakpoints[_arg0] = entry
2020
+ end
2021
+ end
2022
+ return scope
2023
+ end
2024
+ local function persistRegistry(scope)
2025
+ if not pluginRef then
2026
+ return {
2027
+ ok = false,
2028
+ error = "Plugin settings are unavailable; managed breakpoint registry is memory-only.",
2029
+ }
2030
+ end
2031
+ local out = {}
2032
+ for _, entry in breakpoints do
2033
+ local _arg0 = {
2034
+ script_path = entry.script_path,
2035
+ line = entry.line,
2036
+ }
2037
+ table.insert(out, _arg0)
2038
+ end
2039
+ local encodedOk, encoded = pcall(function()
2040
+ return HttpService:JSONEncode(out)
2041
+ end)
2042
+ if not encodedOk or not (type(encoded) == "string") then
2043
+ return {
2044
+ ok = false,
2045
+ error = `Failed to encode managed breakpoint registry: {tostring(encoded)}`,
2046
+ }
2047
+ end
2048
+ if not writeSetting(scope.key, encoded) then
2049
+ return {
2050
+ ok = false,
2051
+ error = "Failed to persist managed breakpoint registry with plugin:SetSetting.",
2052
+ }
2053
+ end
2054
+ local stored = readSetting(scope.key)
2055
+ if stored ~= encoded then
2056
+ return {
2057
+ ok = false,
2058
+ error = "Failed to verify managed breakpoint registry persistence after plugin:SetSetting.",
2059
+ }
2060
+ end
2061
+ return {
2062
+ ok = true,
2063
+ }
2064
+ end
2065
+ local function attachPersistenceWarning(response, persist)
2066
+ if not persist.ok then
2067
+ response.managed_registry_persisted = false
2068
+ response.registry_error = persist.error
2069
+ end
2070
+ return response
2071
+ end
2072
+ local function serviceError(message)
2073
+ local _object = {
2074
+ error = "script_debugger_unavailable",
2075
+ }
2076
+ local _left = "message"
2077
+ local _condition = message
2078
+ if _condition == nil then
2079
+ _condition = "ScriptDebuggerService is unavailable. Enable the Studio Debugger Luau API beta feature and restart Studio."
2080
+ end
2081
+ _object[_left] = _condition
2082
+ _object.betaFeatureRequired = true
2083
+ return _object
2084
+ end
2085
+ local function operationError(errorCode, operation, raw)
2086
+ return {
2087
+ error = errorCode,
2088
+ message = `{operation} failed. The breakpoints tool requires the Studio Debugger Luau API beta feature. ` .. "Enable it in Studio Beta Features and restart/reload Studio, then retry.",
2089
+ rawMessage = tostring(raw),
2090
+ betaFeatureRequired = true,
2091
+ }
2092
+ end
2093
+ local function getService()
2094
+ local provider = game
2095
+ local ok, service = pcall(function()
2096
+ return provider:GetService("ScriptDebuggerService")
2097
+ end)
2098
+ if not ok or not service then
2099
+ return serviceError(`ScriptDebuggerService unavailable: {tostring(service)}`)
2100
+ end
2101
+ return service
2102
+ end
2103
+ local function luauStringLiteral(value)
2104
+ local escaped = (string.gsub(value, "\\", "\\\\"))
2105
+ escaped = (string.gsub(escaped, "\n", "\\n"))
2106
+ escaped = (string.gsub(escaped, "\r", "\\r"))
2107
+ escaped = (string.gsub(escaped, "\t", "\\t"))
2108
+ escaped = (string.gsub(escaped, '"', '\\"'))
2109
+ return `"{escaped}"`
2110
+ end
2111
+ local function buildLogMessage(scriptPath, line, logMessage)
2112
+ local prefix = { luauStringLiteral(LOG_PREFIX), luauStringLiteral(`{scriptPath}:{line}`) }
2113
+ local _logMessage = logMessage
2114
+ local _condition = type(_logMessage) == "string"
2115
+ if _condition then
2116
+ _condition = logMessage ~= ""
2117
+ end
2118
+ if _condition then
2119
+ local _logMessage_1 = logMessage
2120
+ table.insert(prefix, _logMessage_1)
2121
+ end
2122
+ return table.concat(prefix, ", ")
2123
+ end
2124
+ local function listBreakpoints(requestData)
2125
+ loadRegistry(requestData)
2126
+ local out = {}
2127
+ for _, entry in breakpoints do
2128
+ table.insert(out, entry)
2129
+ end
2130
+ return {
2131
+ breakpoints = out,
2132
+ count = #out,
2133
+ }
2134
+ end
2135
+ local function setBreakpoint(requestData)
2136
+ local scope = loadRegistry(requestData)
2137
+ local serviceOrError = getService()
2138
+ local _value = serviceOrError.IsA
2139
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2140
+ return serviceOrError
2141
+ end
2142
+ local service = serviceOrError
2143
+ local scriptPath = requestData.script_path
2144
+ local lineRaw = requestData.line
2145
+ if not (type(scriptPath) == "string") or scriptPath == "" or not (type(lineRaw) == "number") then
2146
+ return {
2147
+ error = "invalid_args",
2148
+ message = "breakpoints action=set requires script_path and line",
2149
+ }
2150
+ end
2151
+ local requestedLine = math.floor(lineRaw)
2152
+ if requestedLine < 1 then
2153
+ return {
2154
+ error = "invalid_line",
2155
+ message = "line must be a 1-based positive number",
2156
+ }
2157
+ end
2158
+ local instance = getInstanceByPath(scriptPath)
2159
+ if not instance then
2160
+ return {
2161
+ error = "script_not_found",
2162
+ script_path = scriptPath,
2163
+ }
2164
+ end
2165
+ if not instance:IsA("LuaSourceContainer") then
2166
+ return {
2167
+ error = "not_a_script",
2168
+ message = `{scriptPath} is {instance.ClassName}, not a LuaSourceContainer`,
2169
+ script_path = scriptPath,
2170
+ }
2171
+ end
2172
+ local _log_message = requestData.log_message
2173
+ local rawLogMessage = if type(_log_message) == "string" then requestData.log_message else nil
2174
+ local hasLogMessage = rawLogMessage ~= nil and rawLogMessage ~= ""
2175
+ local _continue_execution = requestData.continue_execution
2176
+ local continueExecution = if type(_continue_execution) == "boolean" then requestData.continue_execution else hasLogMessage
2177
+ local _enabled = requestData.enabled
2178
+ local enabled = if type(_enabled) == "boolean" then requestData.enabled else true
2179
+ local effectiveLogMessage = if hasLogMessage or continueExecution then buildLogMessage(scriptPath, requestedLine, rawLogMessage) else nil
2180
+ local spec = {
2181
+ Line = requestedLine,
2182
+ Enabled = enabled,
2183
+ ContinueExecution = continueExecution,
2184
+ }
2185
+ local _condition = requestData.condition
2186
+ local _condition_1 = type(_condition) == "string"
2187
+ if _condition_1 then
2188
+ _condition_1 = requestData.condition ~= ""
2189
+ end
2190
+ if _condition_1 then
2191
+ spec.Condition = requestData.condition
2192
+ end
2193
+ if effectiveLogMessage ~= nil then
2194
+ spec.LogMessage = effectiveLogMessage
2195
+ end
2196
+ local ok, result = pcall(function()
2197
+ return service:AddBreakpoint(instance, spec)
2198
+ end)
2199
+ if not ok then
2200
+ return operationError("add_breakpoint_failed", "ScriptDebuggerService:AddBreakpoint", result)
2201
+ end
2202
+ local breakpointResult = result
2203
+ local _line = breakpointResult.Line
2204
+ local actualLine = if type(_line) == "number" then breakpointResult.Line else requestedLine
2205
+ local _verified = breakpointResult.Verified
2206
+ local verified = if type(_verified) == "boolean" then breakpointResult.Verified else nil
2207
+ local _message = breakpointResult.Message
2208
+ local message = if type(_message) == "string" then breakpointResult.Message else nil
2209
+ local entry = {
2210
+ script_path = scriptPath,
2211
+ line = actualLine,
2212
+ requested_line = if actualLine ~= requestedLine then requestedLine else nil,
2213
+ enabled = enabled,
2214
+ condition = spec.Condition,
2215
+ log_message = rawLogMessage,
2216
+ continue_execution = continueExecution,
2217
+ verified = if verified == false then false else nil,
2218
+ message = message,
2219
+ created_at = DateTime.now().UnixTimestampMillis,
2220
+ }
2221
+ local _arg0 = breakpointKey(scriptPath, actualLine)
2222
+ breakpoints[_arg0] = entry
2223
+ return attachPersistenceWarning({
2224
+ ok = true,
2225
+ breakpoint = entry,
2226
+ }, persistRegistry(scope))
2227
+ end
2228
+ local function removeBreakpoint(requestData)
2229
+ local scope = loadRegistry(requestData)
2230
+ local serviceOrError = getService()
2231
+ local _value = serviceOrError.IsA
2232
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2233
+ return serviceOrError
2234
+ end
2235
+ local service = serviceOrError
2236
+ local scriptPath = requestData.script_path
2237
+ local lineRaw = requestData.line
2238
+ if not (type(scriptPath) == "string") or scriptPath == "" or not (type(lineRaw) == "number") then
2239
+ return {
2240
+ error = "invalid_args",
2241
+ message = "breakpoints action=remove requires script_path and line",
2242
+ }
2243
+ end
2244
+ local line = math.floor(lineRaw)
2245
+ if line < 1 then
2246
+ return {
2247
+ error = "invalid_line",
2248
+ message = "line must be a 1-based positive number",
2249
+ }
2250
+ end
2251
+ local instance = getInstanceByPath(scriptPath)
2252
+ if not instance then
2253
+ return {
2254
+ error = "script_not_found",
2255
+ script_path = scriptPath,
2256
+ }
2257
+ end
2258
+ if not instance:IsA("LuaSourceContainer") then
2259
+ return {
2260
+ error = "not_a_script",
2261
+ message = `{scriptPath} is {instance.ClassName}, not a LuaSourceContainer`,
2262
+ script_path = scriptPath,
2263
+ }
2264
+ end
2265
+ local ok, removed = pcall(function()
2266
+ return service:RemoveBreakpoint(instance, line)
2267
+ end)
2268
+ if not ok then
2269
+ return operationError("remove_breakpoint_failed", "ScriptDebuggerService:RemoveBreakpoint", removed)
2270
+ end
2271
+ local _arg0 = breakpointKey(scriptPath, line)
2272
+ breakpoints[_arg0] = nil
2273
+ return attachPersistenceWarning({
2274
+ ok = true,
2275
+ removed = removed,
2276
+ script_path = scriptPath,
2277
+ line = line,
2278
+ }, persistRegistry(scope))
2279
+ end
2280
+ local function clearManagedBreakpoints(requestData)
2281
+ local scope = loadRegistry(requestData)
2282
+ local serviceOrError = getService()
2283
+ local _value = serviceOrError.IsA
2284
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2285
+ return serviceOrError
2286
+ end
2287
+ local service = serviceOrError
2288
+ local cleared = 0
2289
+ local errors = {}
2290
+ for key, entry in breakpoints do
2291
+ local instance = getInstanceByPath(entry.script_path)
2292
+ if not instance or not instance:IsA("LuaSourceContainer") then
2293
+ breakpoints[key] = nil
2294
+ cleared += 1
2295
+ continue
2296
+ end
2297
+ local ok, removedOrError = pcall(function()
2298
+ return service:RemoveBreakpoint(instance, entry.line)
2299
+ end)
2300
+ if ok then
2301
+ breakpoints[key] = nil
2302
+ cleared += 1
2303
+ else
2304
+ local _arg0 = {
2305
+ script_path = entry.script_path,
2306
+ line = entry.line,
2307
+ error = tostring(removedOrError),
2308
+ }
2309
+ table.insert(errors, _arg0)
2310
+ end
2311
+ end
2312
+ if #errors > 0 then
2313
+ return {
2314
+ ok = false,
2315
+ cleared = cleared,
2316
+ errors = errors,
2317
+ }
2318
+ end
2319
+ return attachPersistenceWarning({
2320
+ ok = true,
2321
+ cleared = cleared,
2322
+ }, persistRegistry(scope))
2323
+ end
2324
+ local function clearAllBreakpoints(requestData)
2325
+ local scope = loadRegistry(requestData)
2326
+ local serviceOrError = getService()
2327
+ local _value = serviceOrError.IsA
2328
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2329
+ return serviceOrError
2330
+ end
2331
+ local service = serviceOrError
2332
+ -- ▼ ReadonlyMap.size ▼
2333
+ local _size = 0
2334
+ for _ in breakpoints do
2335
+ _size += 1
2336
+ end
2337
+ -- ▲ ReadonlyMap.size ▲
2338
+ local managedCount = _size
2339
+ local ok, err = pcall(function()
2340
+ return service:ClearBreakpoints()
2341
+ end)
2342
+ if not ok then
2343
+ return operationError("clear_breakpoints_failed", "ScriptDebuggerService:ClearBreakpoints", err)
2344
+ end
2345
+ table.clear(breakpoints)
2346
+ return attachPersistenceWarning({
2347
+ ok = true,
2348
+ cleared_managed = managedCount,
2349
+ }, persistRegistry(scope))
2350
+ end
2351
+ local function clearBreakpoints(requestData)
2352
+ if requestData.clear_all == true then
2353
+ return clearAllBreakpoints(requestData)
2354
+ end
2355
+ return clearManagedBreakpoints(requestData)
2356
+ end
2357
+ local function breakpointsTool(requestData)
2358
+ local action = requestData.action
2359
+ if not (type(action) == "string") or action == "" then
2360
+ return {
2361
+ error = "invalid_args",
2362
+ message = "breakpoints requires action=set|remove|clear|list",
2363
+ }
2364
+ end
2365
+ repeat
2366
+ if action == "set" then
2367
+ return setBreakpoint(requestData)
2368
+ end
2369
+ if action == "remove" then
2370
+ return removeBreakpoint(requestData)
2371
+ end
2372
+ if action == "clear" then
2373
+ return clearBreakpoints(requestData)
2374
+ end
2375
+ if action == "list" then
2376
+ return listBreakpoints(requestData)
2377
+ end
2378
+ return {
2379
+ error = "unknown_action",
2380
+ message = `breakpoints action must be one of: set, remove, clear, list (got {action})`,
2381
+ }
2382
+ until true
2383
+ end
2384
+ return {
2385
+ breakpoints = breakpointsTool,
2386
+ init = init,
2387
+ }
2388
+ ]]></string>
2389
+ </Properties>
2390
+ </Item>
2391
+ <Item class="ModuleScript" referent="8">
1860
2392
  <Properties>
1861
2393
  <string name="Name">BuildHandlers</string>
1862
2394
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2421,7 +2953,7 @@ return {
2421
2953
  ]]></string>
2422
2954
  </Properties>
2423
2955
  </Item>
2424
- <Item class="ModuleScript" referent="8">
2956
+ <Item class="ModuleScript" referent="9">
2425
2957
  <Properties>
2426
2958
  <string name="Name">CaptureHandlers</string>
2427
2959
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2636,7 +3168,7 @@ return {
2636
3168
  ]]></string>
2637
3169
  </Properties>
2638
3170
  </Item>
2639
- <Item class="ModuleScript" referent="9">
3171
+ <Item class="ModuleScript" referent="10">
2640
3172
  <Properties>
2641
3173
  <string name="Name">EvalRuntimeHandlers</string>
2642
3174
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2790,7 +3322,7 @@ return {
2790
3322
  ]]></string>
2791
3323
  </Properties>
2792
3324
  </Item>
2793
- <Item class="ModuleScript" referent="10">
3325
+ <Item class="ModuleScript" referent="11">
2794
3326
  <Properties>
2795
3327
  <string name="Name">InputHandlers</string>
2796
3328
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2990,7 +3522,7 @@ return {
2990
3522
  ]]></string>
2991
3523
  </Properties>
2992
3524
  </Item>
2993
- <Item class="ModuleScript" referent="11">
3525
+ <Item class="ModuleScript" referent="12">
2994
3526
  <Properties>
2995
3527
  <string name="Name">InstanceHandlers</string>
2996
3528
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3497,7 +4029,7 @@ return {
3497
4029
  ]]></string>
3498
4030
  </Properties>
3499
4031
  </Item>
3500
- <Item class="ModuleScript" referent="12">
4032
+ <Item class="ModuleScript" referent="13">
3501
4033
  <Properties>
3502
4034
  <string name="Name">LogHandlers</string>
3503
4035
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3523,7 +4055,7 @@ return {
3523
4055
  ]]></string>
3524
4056
  </Properties>
3525
4057
  </Item>
3526
- <Item class="ModuleScript" referent="13">
4058
+ <Item class="ModuleScript" referent="14">
3527
4059
  <Properties>
3528
4060
  <string name="Name">MemoryHandlers</string>
3529
4061
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3591,7 +4123,7 @@ return {
3591
4123
  ]]></string>
3592
4124
  </Properties>
3593
4125
  </Item>
3594
- <Item class="ModuleScript" referent="14">
4126
+ <Item class="ModuleScript" referent="15">
3595
4127
  <Properties>
3596
4128
  <string name="Name">MetadataHandlers</string>
3597
4129
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4130,7 +4662,7 @@ return {
4130
4662
  ]]></string>
4131
4663
  </Properties>
4132
4664
  </Item>
4133
- <Item class="ModuleScript" referent="15">
4665
+ <Item class="ModuleScript" referent="16">
4134
4666
  <Properties>
4135
4667
  <string name="Name">PropertyHandlers</string>
4136
4668
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4382,7 +4914,7 @@ return {
4382
4914
  ]]></string>
4383
4915
  </Properties>
4384
4916
  </Item>
4385
- <Item class="ModuleScript" referent="16">
4917
+ <Item class="ModuleScript" referent="17">
4386
4918
  <Properties>
4387
4919
  <string name="Name">QueryHandlers</string>
4388
4920
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5330,79 +5862,21 @@ local function compareInstances(requestData)
5330
5862
  b = valB,
5331
5863
  }
5332
5864
  end
5333
- elseif okA then
5334
- table.insert(onlyA, prop)
5335
- elseif okB then
5336
- table.insert(onlyB, prop)
5337
- end
5338
- end
5339
- return {
5340
- instancePathA = instancePathA,
5341
- instancePathB = instancePathB,
5342
- classNameA = instA.ClassName,
5343
- classNameB = instB.ClassName,
5344
- matching = matching,
5345
- differing = differing,
5346
- onlyA = onlyA,
5347
- onlyB = onlyB,
5348
- }
5349
- end
5350
- local function getOutputLog(requestData)
5351
- local _condition = (requestData.maxEntries)
5352
- if _condition == nil then
5353
- _condition = 100
5354
- end
5355
- local maxEntries = _condition
5356
- local messageTypeFilter = requestData.messageType
5357
- local success, result = pcall(function()
5358
- local LogService = game:GetService("LogService")
5359
- local history = LogService:GetLogHistory()
5360
- local allEntries = {}
5361
- for _, entry in history do
5362
- local msgType = tostring(entry.messageType)
5363
- local _condition_1 = messageTypeFilter
5364
- if _condition_1 ~= "" and _condition_1 then
5365
- _condition_1 = msgType ~= messageTypeFilter
5366
- end
5367
- if _condition_1 ~= "" and _condition_1 then
5368
- continue
5369
- end
5370
- local _arg0 = {
5371
- message = entry.message,
5372
- messageType = msgType,
5373
- timestamp = entry.timestamp,
5374
- }
5375
- table.insert(allEntries, _arg0)
5376
- end
5377
- local startIdx = math.max(0, #allEntries - maxEntries)
5378
- local finalEntries = {}
5379
- do
5380
- local i = startIdx
5381
- local _shouldIncrement = false
5382
- while true do
5383
- if _shouldIncrement then
5384
- i += 1
5385
- else
5386
- _shouldIncrement = true
5387
- end
5388
- if not (i < #allEntries) then
5389
- break
5390
- end
5391
- local _arg0 = allEntries[i + 1]
5392
- table.insert(finalEntries, _arg0)
5393
- end
5865
+ elseif okA then
5866
+ table.insert(onlyA, prop)
5867
+ elseif okB then
5868
+ table.insert(onlyB, prop)
5394
5869
  end
5395
- return {
5396
- entries = finalEntries,
5397
- count = #finalEntries,
5398
- totalAvailable = #allEntries,
5399
- }
5400
- end)
5401
- if success then
5402
- return result
5403
5870
  end
5404
5871
  return {
5405
- error = `Failed to get output log: {result}`,
5872
+ instancePathA = instancePathA,
5873
+ instancePathB = instancePathB,
5874
+ classNameA = instA.ClassName,
5875
+ classNameB = instB.ClassName,
5876
+ matching = matching,
5877
+ differing = differing,
5878
+ onlyA = onlyA,
5879
+ onlyB = onlyB,
5406
5880
  }
5407
5881
  end
5408
5882
  return {
@@ -5419,12 +5893,11 @@ return {
5419
5893
  grepScripts = grepScripts,
5420
5894
  getDescendants = getDescendants,
5421
5895
  compareInstances = compareInstances,
5422
- getOutputLog = getOutputLog,
5423
5896
  }
5424
5897
  ]]></string>
5425
5898
  </Properties>
5426
5899
  </Item>
5427
- <Item class="ModuleScript" referent="17">
5900
+ <Item class="ModuleScript" referent="18">
5428
5901
  <Properties>
5429
5902
  <string name="Name">SceneAnalysisHandlers</string>
5430
5903
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5673,7 +6146,7 @@ return {
5673
6146
  ]]></string>
5674
6147
  </Properties>
5675
6148
  </Item>
5676
- <Item class="ModuleScript" referent="18">
6149
+ <Item class="ModuleScript" referent="19">
5677
6150
  <Properties>
5678
6151
  <string name="Name">ScriptHandlers</string>
5679
6152
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6369,7 +6842,470 @@ return {
6369
6842
  ]]></string>
6370
6843
  </Properties>
6371
6844
  </Item>
6372
- <Item class="ModuleScript" referent="19">
6845
+ <Item class="ModuleScript" referent="20">
6846
+ <Properties>
6847
+ <string name="Name">ScriptProfilerHandlers</string>
6848
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
6849
+ local HttpService = game:GetService("HttpService")
6850
+ local Players = game:GetService("Players")
6851
+ local RunService = game:GetService("RunService")
6852
+ local DEFAULT_DURATION_MS = 1000
6853
+ local MIN_DURATION_MS = 100
6854
+ local MAX_DURATION_MS = 15000
6855
+ local DEFAULT_FREQUENCY = 1000
6856
+ local DEFAULT_MAX_FUNCTIONS = 20
6857
+ local function getProfilerService()
6858
+ local provider = game
6859
+ local ok, service = pcall(function()
6860
+ return provider:GetService("ScriptProfilerService")
6861
+ end)
6862
+ if not ok or not service then
6863
+ return {
6864
+ error = "script_profiler_unavailable",
6865
+ message = `ScriptProfilerService is unavailable: {tostring(service)}`,
6866
+ }
6867
+ end
6868
+ return service
6869
+ end
6870
+ local function normalizeDurationMs(value)
6871
+ local _value = value
6872
+ if not (type(_value) == "number") then
6873
+ return DEFAULT_DURATION_MS
6874
+ end
6875
+ return math.clamp(math.floor(value), MIN_DURATION_MS, MAX_DURATION_MS)
6876
+ end
6877
+ local function normalizeFrequency(value)
6878
+ local _value = value
6879
+ if not (type(_value) == "number") then
6880
+ return DEFAULT_FREQUENCY
6881
+ end
6882
+ return math.clamp(math.floor(value), 1, 10000)
6883
+ end
6884
+ local function normalizeMaxFunctions(value)
6885
+ local _value = value
6886
+ if not (type(_value) == "number") then
6887
+ return DEFAULT_MAX_FUNCTIONS
6888
+ end
6889
+ return math.clamp(math.floor(value), 1, 100)
6890
+ end
6891
+ local function normalizeMinTotalUs(requestData)
6892
+ local value = requestData.min_total_us
6893
+ if type(value) == "number" then
6894
+ return math.max(0, value)
6895
+ end
6896
+ return 0
6897
+ end
6898
+ local function stringContains(haystack, needle)
6899
+ return (string.find(string.lower(haystack), string.lower(needle), 1, true)) ~= nil
6900
+ end
6901
+ local function localPlayer()
6902
+ local player = Players.LocalPlayer
6903
+ local started = tick()
6904
+ while not player and tick() - started < 5 do
6905
+ task.wait(0.05)
6906
+ player = Players.LocalPlayer
6907
+ end
6908
+ return player
6909
+ end
6910
+ local function functionDisplayName(func)
6911
+ local _name = func.Name
6912
+ local _condition = type(_name) == "string"
6913
+ if _condition then
6914
+ _condition = func.Name ~= ""
6915
+ end
6916
+ if _condition then
6917
+ return func.Name
6918
+ end
6919
+ local _source = func.Source
6920
+ local _condition_1 = type(_source) == "string"
6921
+ if _condition_1 then
6922
+ _condition_1 = func.Source ~= ""
6923
+ end
6924
+ if _condition_1 then
6925
+ local _line = func.Line
6926
+ local _condition_2 = type(_line) == "number"
6927
+ if _condition_2 then
6928
+ _condition_2 = func.Line > 0
6929
+ end
6930
+ if _condition_2 then
6931
+ return `{func.Source}:{func.Line}`
6932
+ end
6933
+ return func.Source
6934
+ end
6935
+ return "<anonymous>"
6936
+ end
6937
+ local function flagsOf(func)
6938
+ local _flags = func.Flags
6939
+ return if type(_flags) == "number" then func.Flags else 0
6940
+ end
6941
+ local function isNativeFunction(func)
6942
+ return bit32.band(flagsOf(func), 1) ~= 0
6943
+ end
6944
+ local function isPluginFunction(func)
6945
+ if bit32.band(flagsOf(func), 2) ~= 0 then
6946
+ return true
6947
+ end
6948
+ local _source = func.Source
6949
+ local _condition = type(_source) == "string"
6950
+ if _condition then
6951
+ _condition = (string.find(func.Source, "MCPPlugin", 1, true)) ~= nil
6952
+ end
6953
+ return _condition
6954
+ end
6955
+ local function isDebugLabel(func, filter)
6956
+ if filter == nil then
6957
+ return false
6958
+ end
6959
+ local _name = func.Name
6960
+ local _condition = not (type(_name) == "string")
6961
+ if not _condition then
6962
+ _condition = func.Name == ""
6963
+ end
6964
+ if _condition then
6965
+ return false
6966
+ end
6967
+ local _source = func.Source
6968
+ local _condition_1 = not (type(_source) == "string")
6969
+ if not _condition_1 then
6970
+ _condition_1 = func.Source == "" or func.Source == "[C]" or func.Source == "GC"
6971
+ end
6972
+ if _condition_1 then
6973
+ return false
6974
+ end
6975
+ if func.Line ~= nil and func.Line ~= 0 then
6976
+ return false
6977
+ end
6978
+ if isNativeFunction(func) or isPluginFunction(func) then
6979
+ return false
6980
+ end
6981
+ return stringContains(func.Name, filter) or stringContains(func.Source, filter)
6982
+ end
6983
+ local function pctOfCapture(row, durationMs)
6984
+ local captureUs = durationMs * 1000
6985
+ if captureUs <= 0 then
6986
+ return nil
6987
+ end
6988
+ return math.floor((row.total_us / captureUs) * 10000 + 0.5) / 100
6989
+ end
6990
+ local function compactFunction(row, rank, durationMs)
6991
+ local out = {
6992
+ rank = rank,
6993
+ function_index = row.function_index,
6994
+ name = row.name,
6995
+ total_us = math.floor(row.total_us + 0.5),
6996
+ }
6997
+ local pct = pctOfCapture(row, durationMs)
6998
+ if pct ~= nil then
6999
+ out.pct_of_capture = pct
7000
+ end
7001
+ if row.source ~= nil then
7002
+ out.source = row.source
7003
+ end
7004
+ if row.line ~= nil then
7005
+ out.line = row.line
7006
+ end
7007
+ if row.is_native == true then
7008
+ out.is_native = true
7009
+ end
7010
+ if row.is_plugin == true then
7011
+ out.is_plugin = true
7012
+ end
7013
+ if row.is_debug_label == true then
7014
+ out.is_debug_label = true
7015
+ end
7016
+ return out
7017
+ end
7018
+ local function summarizeProfile(rawJson, profile, requestData, durationMs, frequency, eventPlayerName)
7019
+ local _functions = profile.Functions
7020
+ local funcs = if type(_functions) == "table" then profile.Functions else {}
7021
+ local _nodes = profile.Nodes
7022
+ local nodes = if type(_nodes) == "table" then profile.Nodes else {}
7023
+ local _categories = profile.Categories
7024
+ local categories = if type(_categories) == "table" then profile.Categories else {}
7025
+ local maxFunctions = normalizeMaxFunctions(requestData.max_functions)
7026
+ local minTotalUs = normalizeMinTotalUs(requestData)
7027
+ local includeNative = requestData.include_native == true
7028
+ local includePlugin = requestData.include_plugin == true
7029
+ local _filter = requestData.filter
7030
+ local _condition = type(_filter) == "string"
7031
+ if _condition then
7032
+ _condition = requestData.filter ~= ""
7033
+ end
7034
+ local filter = if _condition then requestData.filter else nil
7035
+ local rows = {}
7036
+ local debugRows = {}
7037
+ local omittedNative = 0
7038
+ local omittedPlugin = 0
7039
+ local omittedBelowThreshold = 0
7040
+ local omittedByFilter = 0
7041
+ for i = 0, #funcs - 1 do
7042
+ local func = funcs[i + 1]
7043
+ if not (type(func) == "table") then
7044
+ continue
7045
+ end
7046
+ local info = func
7047
+ local _totalDuration = info.TotalDuration
7048
+ local totalUs = if type(_totalDuration) == "number" then info.TotalDuration else 0
7049
+ local name = functionDisplayName(info)
7050
+ local _object = {
7051
+ function_index = i + 1,
7052
+ name = name,
7053
+ }
7054
+ local _left = "source"
7055
+ local _source = info.Source
7056
+ _object[_left] = if type(_source) == "string" then info.Source else nil
7057
+ local _left_1 = "line"
7058
+ local _line = info.Line
7059
+ _object[_left_1] = if type(_line) == "number" then info.Line else nil
7060
+ _object.total_us = totalUs
7061
+ _object.is_native = if isNativeFunction(info) then true else nil
7062
+ _object.is_plugin = if isPluginFunction(info) then true else nil
7063
+ _object.is_debug_label = if isDebugLabel(info, filter) then true else nil
7064
+ local row = _object
7065
+ if not includeNative and row.is_native == true then
7066
+ omittedNative += 1
7067
+ continue
7068
+ end
7069
+ if not includePlugin and row.is_plugin == true then
7070
+ omittedPlugin += 1
7071
+ continue
7072
+ end
7073
+ if totalUs < minTotalUs then
7074
+ omittedBelowThreshold += 1
7075
+ continue
7076
+ end
7077
+ if filter ~= nil then
7078
+ local _exp = row.name
7079
+ local _condition_1 = row.source
7080
+ if _condition_1 == nil then
7081
+ _condition_1 = ""
7082
+ end
7083
+ local text = `{_exp} {_condition_1}`
7084
+ if not stringContains(text, filter) then
7085
+ omittedByFilter += 1
7086
+ continue
7087
+ end
7088
+ end
7089
+ table.insert(rows, row)
7090
+ if row.is_debug_label == true then
7091
+ table.insert(debugRows, row)
7092
+ end
7093
+ end
7094
+ table.sort(rows, function(a, b)
7095
+ return a.total_us > b.total_us
7096
+ end)
7097
+ table.sort(debugRows, function(a, b)
7098
+ return a.total_us > b.total_us
7099
+ end)
7100
+ local topFunctions = {}
7101
+ do
7102
+ local i = 0
7103
+ local _shouldIncrement = false
7104
+ while true do
7105
+ if _shouldIncrement then
7106
+ i += 1
7107
+ else
7108
+ _shouldIncrement = true
7109
+ end
7110
+ if not (i < math.min(maxFunctions, #rows)) then
7111
+ break
7112
+ end
7113
+ local _arg0 = compactFunction(rows[i + 1], i + 1, durationMs)
7114
+ table.insert(topFunctions, _arg0)
7115
+ end
7116
+ end
7117
+ local debugLabels = {}
7118
+ do
7119
+ local i = 0
7120
+ local _shouldIncrement = false
7121
+ while true do
7122
+ if _shouldIncrement then
7123
+ i += 1
7124
+ else
7125
+ _shouldIncrement = true
7126
+ end
7127
+ if not (i < math.min(maxFunctions, #debugRows)) then
7128
+ break
7129
+ end
7130
+ local _arg0 = compactFunction(debugRows[i + 1], i + 1, durationMs)
7131
+ table.insert(debugLabels, _arg0)
7132
+ end
7133
+ end
7134
+ local categoryNames = {}
7135
+ for i = 0, #categories - 1 do
7136
+ local category = categories[i + 1]
7137
+ if type(category) == "table" then
7138
+ local name = category.Name
7139
+ if type(name) == "string" then
7140
+ table.insert(categoryNames, name)
7141
+ end
7142
+ end
7143
+ end
7144
+ local omitted = {}
7145
+ local hasOmitted = false
7146
+ if omittedNative > 0 then
7147
+ omitted.native = omittedNative
7148
+ hasOmitted = true
7149
+ end
7150
+ if omittedPlugin > 0 then
7151
+ omitted.plugin = omittedPlugin
7152
+ hasOmitted = true
7153
+ end
7154
+ if omittedBelowThreshold > 0 then
7155
+ omitted.below_min_total_us = omittedBelowThreshold
7156
+ hasOmitted = true
7157
+ end
7158
+ if omittedByFilter > 0 then
7159
+ omitted.filtered_out = omittedByFilter
7160
+ hasOmitted = true
7161
+ end
7162
+ local _object = {
7163
+ ok = true,
7164
+ duration_ms = durationMs,
7165
+ frequency = frequency,
7166
+ }
7167
+ local _left = "applied"
7168
+ local _object_1 = {}
7169
+ local _left_1 = "filter"
7170
+ local _condition_1 = filter
7171
+ if _condition_1 == nil then
7172
+ _condition_1 = nil
7173
+ end
7174
+ _object_1[_left_1] = _condition_1
7175
+ _object_1.min_total_us = minTotalUs
7176
+ _object_1.include_native = includeNative
7177
+ _object_1.include_plugin = includePlugin
7178
+ _object_1.max_functions = maxFunctions
7179
+ _object_1.sort = "total_us_desc"
7180
+ _object[_left] = _object_1
7181
+ _object.json_bytes = #rawJson
7182
+ _object.counts = {
7183
+ categories = #categories,
7184
+ nodes = #nodes,
7185
+ functions = #funcs,
7186
+ }
7187
+ _object.top_functions = topFunctions
7188
+ _object.debug_labels = debugLabels
7189
+ local result = _object
7190
+ if #categoryNames > 0 then
7191
+ result.categories = categoryNames
7192
+ end
7193
+ if hasOmitted then
7194
+ result.omitted = omitted
7195
+ end
7196
+ if profile.Version ~= nil then
7197
+ result.version = profile.Version
7198
+ end
7199
+ if profile.SessionStartTime ~= nil or profile.SessionEndTime ~= nil then
7200
+ result.session = {
7201
+ start_time = profile.SessionStartTime,
7202
+ end_time = profile.SessionEndTime,
7203
+ }
7204
+ end
7205
+ if eventPlayerName ~= nil then
7206
+ result.player = eventPlayerName
7207
+ end
7208
+ if requestData.__mcp_include_raw_json == true then
7209
+ result.raw_json = rawJson
7210
+ end
7211
+ return result
7212
+ end
7213
+ local function captureScriptProfiler(requestData)
7214
+ if not RunService:IsRunning() then
7215
+ return {
7216
+ error = "runtime_target_required",
7217
+ message = 'Script profiler capture requires a running playtest target such as target=\"server\" or target=\"client-1\".',
7218
+ }
7219
+ end
7220
+ local serviceOrError = getProfilerService()
7221
+ local _value = serviceOrError.IsA
7222
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
7223
+ return serviceOrError
7224
+ end
7225
+ local service = serviceOrError
7226
+ local durationMs = normalizeDurationMs(requestData.duration_ms)
7227
+ local frequency = normalizeFrequency(requestData.frequency)
7228
+ local isServer = RunService:IsServer()
7229
+ local isClient = RunService:IsClient() and not isServer
7230
+ local player = if isClient then localPlayer() else nil
7231
+ if not isServer and not player then
7232
+ return {
7233
+ error = "client_player_unavailable",
7234
+ message = "Could not resolve Players.LocalPlayer for client profiling.",
7235
+ }
7236
+ end
7237
+ local rawJson
7238
+ local eventPlayerName
7239
+ local connection = service.OnNewData:Connect(function(playerArg, jsonString)
7240
+ if rawJson ~= nil then
7241
+ return nil
7242
+ end
7243
+ rawJson = jsonString
7244
+ if playerArg then
7245
+ eventPlayerName = playerArg.Name
7246
+ end
7247
+ end)
7248
+ local startOk, startErr = pcall(function()
7249
+ if isServer then
7250
+ service:ServerStart(frequency)
7251
+ else
7252
+ service:ClientStart(player, frequency)
7253
+ end
7254
+ end)
7255
+ if not startOk then
7256
+ connection:Disconnect()
7257
+ return {
7258
+ error = "script_profiler_start_failed",
7259
+ message = tostring(startErr),
7260
+ }
7261
+ end
7262
+ task.wait(durationMs / 1000)
7263
+ local stopOk, stopErr = pcall(function()
7264
+ if isServer then
7265
+ service:ServerStop()
7266
+ service:ServerRequestData()
7267
+ else
7268
+ service:ClientStop(player)
7269
+ service:ClientRequestData(player)
7270
+ end
7271
+ end)
7272
+ if not stopOk then
7273
+ connection:Disconnect()
7274
+ return {
7275
+ error = "script_profiler_stop_failed",
7276
+ message = tostring(stopErr),
7277
+ }
7278
+ end
7279
+ local requestedAt = tick()
7280
+ while rawJson == nil and tick() - requestedAt < 5 do
7281
+ task.wait(0.05)
7282
+ end
7283
+ connection:Disconnect()
7284
+ if rawJson == nil then
7285
+ return {
7286
+ error = "script_profiler_data_timeout",
7287
+ message = "ScriptProfilerService did not emit OnNewData after requesting profiler data.",
7288
+ }
7289
+ end
7290
+ local decodeOk, decoded = pcall(function()
7291
+ return HttpService:JSONDecode(rawJson)
7292
+ end)
7293
+ if not decodeOk then
7294
+ return {
7295
+ error = "script_profiler_decode_failed",
7296
+ message = tostring(decoded),
7297
+ json_bytes = #rawJson,
7298
+ }
7299
+ end
7300
+ return summarizeProfile(rawJson, decoded, requestData, durationMs, frequency, eventPlayerName)
7301
+ end
7302
+ return {
7303
+ captureScriptProfiler = captureScriptProfiler,
7304
+ }
7305
+ ]]></string>
7306
+ </Properties>
7307
+ </Item>
7308
+ <Item class="ModuleScript" referent="21">
6373
7309
  <Properties>
6374
7310
  <string name="Name">SerializationHandlers</string>
6375
7311
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6555,7 +7491,7 @@ return {
6555
7491
  ]]></string>
6556
7492
  </Properties>
6557
7493
  </Item>
6558
- <Item class="ModuleScript" referent="20">
7494
+ <Item class="ModuleScript" referent="22">
6559
7495
  <Properties>
6560
7496
  <string name="Name">TestHandlers</string>
6561
7497
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6576,10 +7512,7 @@ local ScriptEditorService = game:GetService("ScriptEditorService")
6576
7512
  local NAV_SIGNAL = "__MCP_NAV__"
6577
7513
  local NAV_RESULT = "__MCP_NAV_RESULT__"
6578
7514
  local testRunning = false
6579
- local outputBuffer = {}
6580
- local logConnection
6581
- local testResult
6582
- local testError
7515
+ local navLogConnection
6583
7516
  local stopListenerScript
6584
7517
  local navResultCallback
6585
7518
  local multiplayerState = {
@@ -6727,6 +7660,12 @@ local function cleanupStopListener()
6727
7660
  stopListenerScript = nil
6728
7661
  end
6729
7662
  end
7663
+ local function disconnectNavLogListener()
7664
+ if navLogConnection then
7665
+ navLogConnection:Disconnect()
7666
+ navLogConnection = nil
7667
+ end
7668
+ end
6730
7669
  local function startPlaytest(requestData)
6731
7670
  local mode = requestData.mode
6732
7671
  local numPlayers = requestData.numPlayers
@@ -6746,10 +7685,7 @@ local function startPlaytest(requestData)
6746
7685
  -- Reset it so subsequent starts don't hit a false "already running".
6747
7686
  if testRunning and not RunService:IsRunning() then
6748
7687
  testRunning = false
6749
- if logConnection then
6750
- logConnection:Disconnect()
6751
- logConnection = nil
6752
- end
7688
+ disconnectNavLogListener()
6753
7689
  cleanupStopListener()
6754
7690
  -- Runtime eval bridges are created by the play server/client plugin
6755
7691
  -- peers and disappear with the play DataModels.
@@ -6760,34 +7696,19 @@ local function startPlaytest(requestData)
6760
7696
  }
6761
7697
  end
6762
7698
  testRunning = true
6763
- outputBuffer = {}
6764
- testResult = nil
6765
- testError = nil
6766
7699
  cleanupStopListener()
6767
- logConnection = LogService.MessageOut:Connect(function(message, messageType)
7700
+ disconnectNavLogListener()
7701
+ navLogConnection = LogService.MessageOut:Connect(function(message)
6768
7702
  local _message = message
6769
- local _arg1 = #NAV_SIGNAL
6770
- if string.sub(_message, 1, _arg1) == NAV_SIGNAL then
6771
- return nil
6772
- end
6773
- local _message_1 = message
6774
- local _arg1_1 = #NAV_RESULT + 1
6775
- if string.sub(_message_1, 1, _arg1_1) == `{NAV_RESULT}:` then
7703
+ local _arg1 = #NAV_RESULT + 1
7704
+ if string.sub(_message, 1, _arg1) == `{NAV_RESULT}:` then
6776
7705
  if navResultCallback then
6777
7706
  local _fn = navResultCallback
6778
- local _message_2 = message
7707
+ local _message_1 = message
6779
7708
  local _arg0 = #NAV_RESULT + 2
6780
- _fn(string.sub(_message_2, _arg0))
7709
+ _fn(string.sub(_message_1, _arg0))
6781
7710
  end
6782
- return nil
6783
7711
  end
6784
- local _outputBuffer = outputBuffer
6785
- local _arg0 = {
6786
- message = message,
6787
- messageType = messageType.Name,
6788
- timestamp = tick(),
6789
- }
6790
- table.insert(_outputBuffer, _arg0)
6791
7712
  end)
6792
7713
  local injected, injErr = pcall(function()
6793
7714
  return injectStopListener()
@@ -6802,15 +7723,10 @@ local function startPlaytest(requestData)
6802
7723
  end
6803
7724
  return StudioTestService:ExecuteRunModeAsync({})
6804
7725
  end)
6805
- if ok then
6806
- testResult = result
6807
- else
6808
- testError = tostring(result)
6809
- end
6810
- if logConnection then
6811
- logConnection:Disconnect()
6812
- logConnection = nil
7726
+ if not ok then
7727
+ warn(`[robloxstudio-mcp] Playtest ended with error: {result}`)
6813
7728
  end
7729
+ disconnectNavLogListener()
6814
7730
  testRunning = false
6815
7731
  cleanupStopListener()
6816
7732
  end)
@@ -6887,20 +7803,6 @@ local function stopPlaytest(_requestData)
6887
7803
  message = "Playtest stopped.",
6888
7804
  }
6889
7805
  end
6890
- local function getPlaytestOutput(_requestData)
6891
- local _object = {
6892
- isRunning = testRunning,
6893
- }
6894
- local _left = "output"
6895
- local _array = {}
6896
- local _length = #_array
6897
- table.move(outputBuffer, 1, #outputBuffer, _length + 1, _array)
6898
- _object[_left] = _array
6899
- _object.outputCount = #outputBuffer
6900
- _object.testResult = if testResult ~= nil then tostring(testResult) else nil
6901
- _object.testError = testError
6902
- return _object
6903
- end
6904
7806
  local function multiplayerTestStart(requestData)
6905
7807
  if RunService:IsRunning() then
6906
7808
  return {
@@ -7155,7 +8057,6 @@ end
7155
8057
  return {
7156
8058
  startPlaytest = startPlaytest,
7157
8059
  stopPlaytest = stopPlaytest,
7158
- getPlaytestOutput = getPlaytestOutput,
7159
8060
  multiplayerTestStart = multiplayerTestStart,
7160
8061
  multiplayerTestState = multiplayerTestState,
7161
8062
  multiplayerTestAddPlayers = multiplayerTestAddPlayers,
@@ -7167,7 +8068,7 @@ return {
7167
8068
  </Properties>
7168
8069
  </Item>
7169
8070
  </Item>
7170
- <Item class="ModuleScript" referent="21">
8071
+ <Item class="ModuleScript" referent="23">
7171
8072
  <Properties>
7172
8073
  <string name="Name">HttpDiagnostics</string>
7173
8074
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7248,7 +8149,7 @@ return {
7248
8149
  ]]></string>
7249
8150
  </Properties>
7250
8151
  </Item>
7251
- <Item class="ModuleScript" referent="22">
8152
+ <Item class="ModuleScript" referent="24">
7252
8153
  <Properties>
7253
8154
  <string name="Name">LuauExec</string>
7254
8155
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7682,7 +8583,7 @@ return {
7682
8583
  ]]></string>
7683
8584
  </Properties>
7684
8585
  </Item>
7685
- <Item class="ModuleScript" referent="23">
8586
+ <Item class="ModuleScript" referent="25">
7686
8587
  <Properties>
7687
8588
  <string name="Name">Recording</string>
7688
8589
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7712,7 +8613,7 @@ return {
7712
8613
  ]]></string>
7713
8614
  </Properties>
7714
8615
  </Item>
7715
- <Item class="ModuleScript" referent="24">
8616
+ <Item class="ModuleScript" referent="26">
7716
8617
  <Properties>
7717
8618
  <string name="Name">RenderMonitor</string>
7718
8619
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7780,7 +8681,7 @@ return {
7780
8681
  ]]></string>
7781
8682
  </Properties>
7782
8683
  </Item>
7783
- <Item class="ModuleScript" referent="25">
8684
+ <Item class="ModuleScript" referent="27">
7784
8685
  <Properties>
7785
8686
  <string name="Name">RuntimeLogBuffer</string>
7786
8687
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7833,6 +8734,43 @@ local function dropOldestUntilFits(incomingBytes)
7833
8734
  totalDropped += 1
7834
8735
  end
7835
8736
  end
8737
+ local function pushEntry(msg, t, ts)
8738
+ if ts == nil then
8739
+ ts = nowSec()
8740
+ end
8741
+ local bytes = #msg
8742
+ dropOldestUntilFits(bytes)
8743
+ local _arg0 = {
8744
+ seq = nextSeq,
8745
+ ts = ts,
8746
+ level = levelTag(t),
8747
+ message = msg,
8748
+ }
8749
+ table.insert(entries, _arg0)
8750
+ nextSeq += 1
8751
+ totalBytes += bytes
8752
+ end
8753
+ local function seedRuntimeHistory()
8754
+ if not RunService:IsRunning() then
8755
+ return nil
8756
+ end
8757
+ local ok, history = pcall(function()
8758
+ return LogService:GetLogHistory()
8759
+ end)
8760
+ if not ok then
8761
+ return nil
8762
+ end
8763
+ for _, entry in history do
8764
+ local _message = entry.message
8765
+ if not (type(_message) == "string") then
8766
+ continue
8767
+ end
8768
+ local _exp = entry.message
8769
+ local _exp_1 = entry.messageType
8770
+ local _timestamp = entry.timestamp
8771
+ pushEntry(_exp, _exp_1, if type(_timestamp) == "number" then entry.timestamp else nil)
8772
+ end
8773
+ end
7836
8774
  local function install()
7837
8775
  if installed then
7838
8776
  return nil
@@ -7841,18 +8779,12 @@ local function install()
7841
8779
  return nil
7842
8780
  end
7843
8781
  installed = true
8782
+ -- Play peers can emit startup logs before the plugin finishes loading.
8783
+ -- Seed from per-DataModel LogHistory so get_runtime_logs can still see
8784
+ -- those early messages; skip edit mode to avoid stale prior-session logs.
8785
+ seedRuntimeHistory()
7844
8786
  LogService.MessageOut:Connect(function(msg, t)
7845
- local bytes = #msg
7846
- dropOldestUntilFits(bytes)
7847
- local _arg0 = {
7848
- seq = nextSeq,
7849
- ts = nowSec(),
7850
- level = levelTag(t),
7851
- message = msg,
7852
- }
7853
- table.insert(entries, _arg0)
7854
- nextSeq += 1
7855
- totalBytes += bytes
8787
+ pushEntry(msg, t)
7856
8788
  end)
7857
8789
  end
7858
8790
  local function detectPeer()
@@ -7961,7 +8893,7 @@ return {
7961
8893
  ]]></string>
7962
8894
  </Properties>
7963
8895
  </Item>
7964
- <Item class="ModuleScript" referent="26">
8896
+ <Item class="ModuleScript" referent="28">
7965
8897
  <Properties>
7966
8898
  <string name="Name">ServerUrlSettings</string>
7967
8899
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -8037,11 +8969,11 @@ return {
8037
8969
  ]]></string>
8038
8970
  </Properties>
8039
8971
  </Item>
8040
- <Item class="ModuleScript" referent="27">
8972
+ <Item class="ModuleScript" referent="29">
8041
8973
  <Properties>
8042
8974
  <string name="Name">State</string>
8043
8975
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
8044
- local CURRENT_VERSION = "2.16.4"
8976
+ local CURRENT_VERSION = "2.17.0"
8045
8977
  local PLUGIN_VARIANT = "main"
8046
8978
  local MAX_CONNECTIONS = 5
8047
8979
  local BASE_PORT = 58741
@@ -8135,7 +9067,7 @@ return {
8135
9067
  ]]></string>
8136
9068
  </Properties>
8137
9069
  </Item>
8138
- <Item class="ModuleScript" referent="28">
9070
+ <Item class="ModuleScript" referent="30">
8139
9071
  <Properties>
8140
9072
  <string name="Name">StopPlayMonitor</string>
8141
9073
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -8433,7 +9365,7 @@ return {
8433
9365
  ]]></string>
8434
9366
  </Properties>
8435
9367
  </Item>
8436
- <Item class="ModuleScript" referent="29">
9368
+ <Item class="ModuleScript" referent="31">
8437
9369
  <Properties>
8438
9370
  <string name="Name">UI</string>
8439
9371
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9204,7 +10136,7 @@ return {
9204
10136
  ]]></string>
9205
10137
  </Properties>
9206
10138
  </Item>
9207
- <Item class="ModuleScript" referent="30">
10139
+ <Item class="ModuleScript" referent="32">
9208
10140
  <Properties>
9209
10141
  <string name="Name">Utils</string>
9210
10142
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9734,11 +10666,11 @@ return {
9734
10666
  </Properties>
9735
10667
  </Item>
9736
10668
  </Item>
9737
- <Item class="Folder" referent="34">
10669
+ <Item class="Folder" referent="36">
9738
10670
  <Properties>
9739
10671
  <string name="Name">include</string>
9740
10672
  </Properties>
9741
- <Item class="ModuleScript" referent="31">
10673
+ <Item class="ModuleScript" referent="33">
9742
10674
  <Properties>
9743
10675
  <string name="Name">Promise</string>
9744
10676
  <string name="Source"><![CDATA[--[[
@@ -11812,7 +12744,7 @@ return Promise
11812
12744
  ]]></string>
11813
12745
  </Properties>
11814
12746
  </Item>
11815
- <Item class="ModuleScript" referent="32">
12747
+ <Item class="ModuleScript" referent="34">
11816
12748
  <Properties>
11817
12749
  <string name="Name">RuntimeLib</string>
11818
12750
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -12079,15 +13011,15 @@ return TS
12079
13011
  </Properties>
12080
13012
  </Item>
12081
13013
  </Item>
12082
- <Item class="Folder" referent="35">
13014
+ <Item class="Folder" referent="37">
12083
13015
  <Properties>
12084
13016
  <string name="Name">node_modules</string>
12085
13017
  </Properties>
12086
- <Item class="Folder" referent="36">
13018
+ <Item class="Folder" referent="38">
12087
13019
  <Properties>
12088
13020
  <string name="Name">@rbxts</string>
12089
13021
  </Properties>
12090
- <Item class="ModuleScript" referent="33">
13022
+ <Item class="ModuleScript" referent="35">
12091
13023
  <Properties>
12092
13024
  <string name="Name">services</string>
12093
13025
  <string name="Source"><![CDATA[return setmetatable({}, {