@chrrxs/robloxstudio-mcp-inspector 2.15.1 → 2.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -105,6 +105,7 @@ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandl
105
105
  local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
106
106
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
107
107
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
108
+ local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
108
109
  local LuauExec = TS.import(script, script.Parent, "LuauExec")
109
110
  local State = TS.import(script, script.Parent, "State")
110
111
  local StudioTestService = game:GetService("StudioTestService")
@@ -167,6 +168,7 @@ local BROKER_OWNER_ATTRIBUTE = "__MCPBrokerOwner"
167
168
  -- cache / etc. lives there) so the server peer alone can't satisfy them.
168
169
  local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
169
170
  ["/api/execute-luau"] = true,
171
+ ["/api/eval-runtime"] = true,
170
172
  ["/api/get-runtime-logs"] = true,
171
173
  ["/api/get-memory-breakdown"] = true,
172
174
  ["/api/get-scene-analysis"] = true,
@@ -247,8 +249,8 @@ local function handleGetRuntimeLogs(data)
247
249
  local since = d.since
248
250
  local tail = d.tail
249
251
  local filter = d.filter
250
- -- "client" is the generic peer tag; MCP-side aggregator overrides with
251
- -- the specific role (e.g. "client-1") on target=all fan-out.
252
+ -- "client" is the generic capture tag; MCP-side aggregation overrides it
253
+ -- with the specific role (e.g. "client-1") for capturedBy.
252
254
  return RuntimeLogBuffer.query({
253
255
  since = since,
254
256
  tail = tail,
@@ -366,6 +368,9 @@ local function setupClientBroker()
366
368
  if payload and payload.endpoint == "/api/execute-luau" then
367
369
  return handleExecuteLuau(payload.data)
368
370
  end
371
+ if payload and payload.endpoint == "/api/eval-runtime" then
372
+ return EvalRuntimeHandlers.evalRuntime(payload.data or {})
373
+ end
369
374
  -- Legacy: raw execute-luau payload at the top level.
370
375
  return handleExecuteLuau(payload)
371
376
  end
@@ -562,6 +567,7 @@ local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
562
567
  local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
563
568
  local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
564
569
  local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
570
+ local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
565
571
  -- Per-plugin-load random GUID. Used as the /poll URL param so the server
566
572
  -- can tell our polls apart from any other plugin's polls. Not user-facing —
567
573
  -- MCP tools and the LLM operate on instanceId (the place identifier).
@@ -668,6 +674,7 @@ local routeMap = {
668
674
  ["/api/get-tagged"] = MetadataHandlers.getTagged,
669
675
  ["/api/get-selection"] = MetadataHandlers.getSelection,
670
676
  ["/api/execute-luau"] = MetadataHandlers.executeLuau,
677
+ ["/api/eval-runtime"] = EvalRuntimeHandlers.evalRuntime,
671
678
  ["/api/undo"] = MetadataHandlers.undo,
672
679
  ["/api/redo"] = MetadataHandlers.redo,
673
680
  ["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
@@ -1242,9 +1249,10 @@ bf.Archivable = false\
1242
1249
  bf.Parent = ServerScriptService\
1243
1250
  bf.OnInvoke = function(payload)\
1244
1251
  if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then\
1245
- return false, "payload must be a ModuleScript instance"\
1252
+ return \{ ok = false, value = "payload must be a ModuleScript instance" \}\
1246
1253
  end\
1247
- return pcall(require, payload)\
1254
+ local ok, value = pcall(require, payload)\
1255
+ return \{ ok = ok, value = value \}\
1248
1256
  end\
1249
1257
  `
1250
1258
  local CLIENT_BRIDGE_SOURCE = `\
@@ -1268,9 +1276,10 @@ bf.Archivable = false\
1268
1276
  bf.Parent = ReplicatedStorage\
1269
1277
  bf.OnInvoke = function(payload)\
1270
1278
  if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then\
1271
- return false, "payload must be a ModuleScript instance"\
1279
+ return \{ ok = false, value = "payload must be a ModuleScript instance" \}\
1272
1280
  end\
1273
- return pcall(require, payload)\
1281
+ local ok, value = pcall(require, payload)\
1282
+ return \{ ok = ok, value = value \}\
1274
1283
  end\
1275
1284
  `
1276
1285
  -- Stamp written onto each installed bridge Script so we can tell whether the
@@ -1287,9 +1296,9 @@ local function computeBridgeStamp()
1287
1296
  for i = 1, #combined do
1288
1297
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1289
1298
  end
1290
- -- "2.15.1" is replaced with the package version at package time
1299
+ -- "2.15.2" is replaced with the package version at package time
1291
1300
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1292
- return `{tostring(h)}-2.15.1`
1301
+ return `{tostring(h)}-2.15.2`
1293
1302
  end
1294
1303
  local BRIDGE_STAMP = computeBridgeStamp()
1295
1304
  local function setSource(scriptInst, source)
@@ -2466,6 +2475,132 @@ return {
2466
2475
  </Properties>
2467
2476
  </Item>
2468
2477
  <Item class="ModuleScript" referent="9">
2478
+ <Properties>
2479
+ <string name="Name">EvalRuntimeHandlers</string>
2480
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
2481
+ local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
2482
+ local _services = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services")
2483
+ local LogService = _services.LogService
2484
+ local ReplicatedStorage = _services.ReplicatedStorage
2485
+ local RunService = _services.RunService
2486
+ local ServerScriptService = _services.ServerScriptService
2487
+ local BRIDGE_NAMES = TS.import(script, script.Parent.Parent, "EvalBridges").BRIDGE_NAMES
2488
+ local LuauExec = TS.import(script, script.Parent.Parent, "LuauExec")
2489
+ local PAYLOAD_INSTANCE_NAME = "__MCPEvalPayload"
2490
+ local function getBridgeConfig()
2491
+ if not RunService:IsRunning() then
2492
+ return {
2493
+ error = "eval_*_runtime requires a running playtest.",
2494
+ }
2495
+ end
2496
+ if RunService:IsServer() then
2497
+ return {
2498
+ service = ServerScriptService,
2499
+ bridgeName = BRIDGE_NAMES.serverLocal,
2500
+ missingError = "ServerEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically (including for manually-started playtests); if a playtest is running and you still see this, reconnect the plugin in the edit window so the bridge reinstalls, then start the playtest again.",
2501
+ }
2502
+ end
2503
+ return {
2504
+ service = ReplicatedStorage,
2505
+ bridgeName = BRIDGE_NAMES.clientLocal,
2506
+ missingError = "ClientEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically (including for manually-started playtests); if a playtest is running and you still see this, reconnect the plugin in the edit window so the bridge reinstalls, then start the playtest again.",
2507
+ }
2508
+ end
2509
+ local function evalRuntime(requestData)
2510
+ local code = requestData.code
2511
+ if not (code ~= "" and code) or code == "" then
2512
+ return {
2513
+ error = "Code is required",
2514
+ }
2515
+ end
2516
+ local config = getBridgeConfig()
2517
+ if config.error ~= nil then
2518
+ return {
2519
+ bridge = "missing",
2520
+ error = config.error,
2521
+ }
2522
+ end
2523
+ local bridge = config.service:FindFirstChild(config.bridgeName)
2524
+ if not bridge or not bridge:IsA("BindableFunction") then
2525
+ return {
2526
+ bridge = "missing",
2527
+ error = config.missingError,
2528
+ }
2529
+ end
2530
+ local m = Instance.new("ModuleScript")
2531
+ m.Name = PAYLOAD_INSTANCE_NAME
2532
+ local userLines = LuauExec.countLines(code)
2533
+ local wrapped = LuauExec.buildWrapper(code, PAYLOAD_INSTANCE_NAME)
2534
+ local okSet, setErr = pcall(function()
2535
+ m.Source = wrapped
2536
+ end)
2537
+ if not okSet then
2538
+ m:Destroy()
2539
+ return {
2540
+ bridge = "ok",
2541
+ ok = false,
2542
+ error = `ModuleScript Source set failed: {tostring(setErr)}`,
2543
+ }
2544
+ end
2545
+ m.Parent = game:GetService("Workspace")
2546
+ local historyStart = #LogService:GetLogHistory()
2547
+ local invokeOk, invokeResult = pcall(function()
2548
+ return bridge:Invoke(m)
2549
+ end)
2550
+ m:Destroy()
2551
+ if not invokeOk then
2552
+ return {
2553
+ bridge = "ok",
2554
+ ok = false,
2555
+ error = tostring(invokeResult),
2556
+ }
2557
+ end
2558
+ if not (type(invokeResult) == "table") then
2559
+ return {
2560
+ bridge = "ok",
2561
+ ok = false,
2562
+ error = `Eval bridge returned invalid result: {tostring(invokeResult)}`,
2563
+ }
2564
+ end
2565
+ local bridgeResult = invokeResult
2566
+ if bridgeResult.ok ~= true then
2567
+ return {
2568
+ bridge = "ok",
2569
+ ok = false,
2570
+ error = LuauExec.recoverPayloadRequireError(bridgeResult.value, userLines, PAYLOAD_INSTANCE_NAME, historyStart),
2571
+ }
2572
+ end
2573
+ local inner = bridgeResult.value
2574
+ if not (type(inner) == "table") then
2575
+ return {
2576
+ bridge = "ok",
2577
+ ok = true,
2578
+ result = if inner == nil then nil else LuauExec.formatReturnValue(inner),
2579
+ }
2580
+ end
2581
+ local r = inner
2582
+ local ok = r.ok == true
2583
+ local _object = {
2584
+ bridge = "ok",
2585
+ ok = ok,
2586
+ result = if ok and r.value ~= nil then LuauExec.formatReturnValue(r.value) else nil,
2587
+ error = if not ok then tostring(r.value) else nil,
2588
+ }
2589
+ local _left = "output"
2590
+ local _condition = r.output
2591
+ if _condition == nil then
2592
+ _condition = {}
2593
+ end
2594
+ _object[_left] = _condition
2595
+ return _object
2596
+ end
2597
+ return {
2598
+ evalRuntime = evalRuntime,
2599
+ }
2600
+ ]]></string>
2601
+ </Properties>
2602
+ </Item>
2603
+ <Item class="ModuleScript" referent="10">
2469
2604
  <Properties>
2470
2605
  <string name="Name">InputHandlers</string>
2471
2606
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2665,7 +2800,7 @@ return {
2665
2800
  ]]></string>
2666
2801
  </Properties>
2667
2802
  </Item>
2668
- <Item class="ModuleScript" referent="10">
2803
+ <Item class="ModuleScript" referent="11">
2669
2804
  <Properties>
2670
2805
  <string name="Name">InstanceHandlers</string>
2671
2806
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3172,7 +3307,7 @@ return {
3172
3307
  ]]></string>
3173
3308
  </Properties>
3174
3309
  </Item>
3175
- <Item class="ModuleScript" referent="11">
3310
+ <Item class="ModuleScript" referent="12">
3176
3311
  <Properties>
3177
3312
  <string name="Name">LogHandlers</string>
3178
3313
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3182,16 +3317,15 @@ local function getRuntimeLogs(requestData)
3182
3317
  local since = requestData.since
3183
3318
  local tail = requestData.tail
3184
3319
  local filter = requestData.filter
3185
- -- Plugin-side peer tag is generic ("edit"|"server"|"client"). The MCP-side
3186
- -- aggregator overrides it with the specific instance role (e.g. "client-1")
3187
- -- during fan-out for target=all, so this value is only authoritative for
3188
- -- the single-peer query path.
3189
- local peer = RuntimeLogBuffer.detectPeer()
3320
+ -- This is the buffer that captured the LogService event, not necessarily
3321
+ -- the script-origin peer. Ordinary playtests share/reflect logs across
3322
+ -- edit/server/client LogService buffers.
3323
+ local capturedBy = RuntimeLogBuffer.detectPeer()
3190
3324
  return RuntimeLogBuffer.query({
3191
3325
  since = since,
3192
3326
  tail = tail,
3193
3327
  filter = filter,
3194
- }, peer)
3328
+ }, capturedBy)
3195
3329
  end
3196
3330
  return {
3197
3331
  getRuntimeLogs = getRuntimeLogs,
@@ -3199,7 +3333,7 @@ return {
3199
3333
  ]]></string>
3200
3334
  </Properties>
3201
3335
  </Item>
3202
- <Item class="ModuleScript" referent="12">
3336
+ <Item class="ModuleScript" referent="13">
3203
3337
  <Properties>
3204
3338
  <string name="Name">MemoryHandlers</string>
3205
3339
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3267,7 +3401,7 @@ return {
3267
3401
  ]]></string>
3268
3402
  </Properties>
3269
3403
  </Item>
3270
- <Item class="ModuleScript" referent="13">
3404
+ <Item class="ModuleScript" referent="14">
3271
3405
  <Properties>
3272
3406
  <string name="Name">MetadataHandlers</string>
3273
3407
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3806,7 +3940,7 @@ return {
3806
3940
  ]]></string>
3807
3941
  </Properties>
3808
3942
  </Item>
3809
- <Item class="ModuleScript" referent="14">
3943
+ <Item class="ModuleScript" referent="15">
3810
3944
  <Properties>
3811
3945
  <string name="Name">PropertyHandlers</string>
3812
3946
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4058,7 +4192,7 @@ return {
4058
4192
  ]]></string>
4059
4193
  </Properties>
4060
4194
  </Item>
4061
- <Item class="ModuleScript" referent="15">
4195
+ <Item class="ModuleScript" referent="16">
4062
4196
  <Properties>
4063
4197
  <string name="Name">QueryHandlers</string>
4064
4198
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5100,7 +5234,7 @@ return {
5100
5234
  ]]></string>
5101
5235
  </Properties>
5102
5236
  </Item>
5103
- <Item class="ModuleScript" referent="16">
5237
+ <Item class="ModuleScript" referent="17">
5104
5238
  <Properties>
5105
5239
  <string name="Name">SceneAnalysisHandlers</string>
5106
5240
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5349,7 +5483,7 @@ return {
5349
5483
  ]]></string>
5350
5484
  </Properties>
5351
5485
  </Item>
5352
- <Item class="ModuleScript" referent="17">
5486
+ <Item class="ModuleScript" referent="18">
5353
5487
  <Properties>
5354
5488
  <string name="Name">ScriptHandlers</string>
5355
5489
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6045,7 +6179,7 @@ return {
6045
6179
  ]]></string>
6046
6180
  </Properties>
6047
6181
  </Item>
6048
- <Item class="ModuleScript" referent="18">
6182
+ <Item class="ModuleScript" referent="19">
6049
6183
  <Properties>
6050
6184
  <string name="Name">SerializationHandlers</string>
6051
6185
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6231,7 +6365,7 @@ return {
6231
6365
  ]]></string>
6232
6366
  </Properties>
6233
6367
  </Item>
6234
- <Item class="ModuleScript" referent="19">
6368
+ <Item class="ModuleScript" referent="20">
6235
6369
  <Properties>
6236
6370
  <string name="Name">TestHandlers</string>
6237
6371
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6864,7 +6998,7 @@ return {
6864
6998
  </Properties>
6865
6999
  </Item>
6866
7000
  </Item>
6867
- <Item class="ModuleScript" referent="20">
7001
+ <Item class="ModuleScript" referent="21">
6868
7002
  <Properties>
6869
7003
  <string name="Name">LuauExec</string>
6870
7004
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6873,10 +7007,12 @@ return {
6873
7007
  -- and the play-client peer (ClientBroker.handleExecuteLuau). Three things this
6874
7008
  -- module owns:
6875
7009
  --
6876
- -- 1. The IIFE wrapper that captures print/warn, runs user code in xpcall,
6877
- -- and always returns { ok, value, output } so the ModuleScript itself
6878
- -- always returns exactly one value (otherwise `print("hi")` with no
6879
- -- return would fail with "Module code did not return exactly one value").
7010
+ -- 1. The IIFE wrapper that captures print/warn, wraps require() so nested
7011
+ -- ModuleScript load failures can recover the real LogService diagnostic,
7012
+ -- runs user code in xpcall, and always returns { ok, value, output } so
7013
+ -- the ModuleScript itself always returns exactly one value (otherwise
7014
+ -- `print("hi")` with no return would fail with "Module code did not
7015
+ -- return exactly one value").
6880
7016
  --
6881
7017
  -- 2. The loadstring-then-ModuleScript-require fallback, with the parse-error
6882
7018
  -- recovery hack that pulls the real diagnostic from LogService.
@@ -6894,14 +7030,14 @@ return {
6894
7030
  local HttpService = game:GetService("HttpService")
6895
7031
  local LogService = game:GetService("LogService")
6896
7032
  local PAYLOAD_INSTANCE_NAME = "__MCPExecLuauPayload"
6897
- local PAYLOAD_PATH_PREFIX = `Workspace.{PAYLOAD_INSTANCE_NAME}:`
7033
+ local REQUIRE_GENERIC_ERROR = "Requested module experienced an error while loading"
6898
7034
  -- Number of lines the wrapper emits BEFORE the first line of user code.
6899
7035
  -- Used both inside the wrapper (Luau __mcp_LINE_OFFSET) and on the TS side
6900
7036
  -- (remapPayloadLines, for compile errors recovered from LogService) so user
6901
7037
  -- code errors report user-relative line numbers instead of the inflated
6902
- -- "line 23" the wrapper would otherwise expose. If you reorder buildWrapper's
6903
- -- prefix lines, update this constant — there's a self-check below.
6904
- local WRAPPER_LINE_OFFSET = 23
7038
+ -- "line 49" the wrapper would otherwise expose. If you reorder buildWrapper's
7039
+ -- prefix lines, update this constant.
7040
+ local WRAPPER_LINE_OFFSET = 84
6905
7041
  -- Count source lines so the wrapper can filter traceback frames that fall
6906
7042
  -- outside the user code range (the wrapper's own preamble/postamble lines).
6907
7043
  local function countLines(s)
@@ -6926,20 +7062,31 @@ local function countLines(s)
6926
7062
  end
6927
7063
  return n
6928
7064
  end
6929
- local function buildWrapper(code)
7065
+ local function luaPatternEscape(s)
7066
+ local escaped = string.gsub(s, "([^%w])", "%%%1")
7067
+ return escaped
7068
+ end
7069
+ local function buildWrapper(code, payloadInstanceName)
7070
+ if payloadInstanceName == nil then
7071
+ payloadInstanceName = PAYLOAD_INSTANCE_NAME
7072
+ end
6930
7073
  -- If you reorder the prefix lines below, update WRAPPER_LINE_OFFSET to
6931
7074
  -- match the number of lines emitted BEFORE the ${code} substitution.
6932
7075
  -- The constant is mirrored inside the wrapper (__mcp_LINE_OFFSET) and
6933
7076
  -- used by remapPayloadLines on the TS side.
6934
7077
  local userLines = countLines(code)
7078
+ local payloadPattern = luaPatternEscape(payloadInstanceName)
6935
7079
  return `return ((function()\
6936
7080
  \tlocal __mcp_traceback\
6937
7081
  \tlocal __mcp_remap\
6938
7082
  \tlocal __mcp_LINE_OFFSET = {WRAPPER_LINE_OFFSET}\
6939
7083
  \tlocal __mcp_USER_LINES = {userLines}\
7084
+ \tlocal __mcp_LogService = game:GetService("LogService")\
7085
+ \tlocal __mcp_REQUIRE_GENERIC = "{REQUIRE_GENERIC_ERROR}"\
6940
7086
  \tlocal __mcp_output = \{\}\
6941
7087
  \tlocal __mcp_real_print = print\
6942
7088
  \tlocal __mcp_real_warn = warn\
7089
+ \tlocal __mcp_real_require = require\
6943
7090
  \tlocal print = function(...)\
6944
7091
  \t\t__mcp_real_print(...)\
6945
7092
  \t\tlocal args = \{...\}\
@@ -6954,6 +7101,64 @@ local function buildWrapper(code)
6954
7101
  \t\tfor i, a in ipairs(args) do parts[i] = tostring(a) end\
6955
7102
  \t\ttable.insert(__mcp_output, "[warn] " .. table.concat(parts, "\\t"))\
6956
7103
  \tend\
7104
+ \tlocal function __mcp_is_stack_noise(msg)\
7105
+ \t\treturn msg == "Stack Begin" or msg == "Stack End" or string.sub(msg, 1, 8) == "Script '"\
7106
+ \tend\
7107
+ \tlocal function __mcp_is_actionable_require_log(entry)\
7108
+ \t\tif not entry or entry.messageType ~= Enum.MessageType.MessageError then return false end\
7109
+ \t\tlocal msg = tostring(entry.message)\
7110
+ \t\treturn msg ~= __mcp_REQUIRE_GENERIC and not __mcp_is_stack_noise(msg)\
7111
+ \tend\
7112
+ \tlocal function __mcp_entry_mentions_module(entry, module_path)\
7113
+ \t\tif not entry or not module_path or module_path == "" then return false end\
7114
+ \t\treturn string.find(tostring(entry.message), module_path, 1, true) ~= nil\
7115
+ \tend\
7116
+ \tlocal function __mcp_prior_module_error(hist, module_path)\
7117
+ \t\tif not module_path or module_path == "" then return nil end\
7118
+ \t\tfor i = #hist, 1, -1 do\
7119
+ \t\t\tlocal entry = hist[i]\
7120
+ \t\t\tif __mcp_entry_mentions_module(entry, module_path) then\
7121
+ \t\t\t\tif __mcp_is_actionable_require_log(entry) then\
7122
+ \t\t\t\t\treturn tostring(entry.message)\
7123
+ \t\t\t\tend\
7124
+ \t\t\t\tfor j = i - 1, math.max(1, i - 6), -1 do\
7125
+ \t\t\t\t\tlocal previous = hist[j]\
7126
+ \t\t\t\t\tif __mcp_is_actionable_require_log(previous) then\
7127
+ \t\t\t\t\t\treturn tostring(previous.message)\
7128
+ \t\t\t\t\tend\
7129
+ \t\t\t\tend\
7130
+ \t\t\tend\
7131
+ \t\tend\
7132
+ \t\treturn nil\
7133
+ \tend\
7134
+ \tlocal function __mcp_recover_require_error(err, history_start, module)\
7135
+ \t\tlocal err_msg = tostring(err)\
7136
+ \t\tif err_msg ~= __mcp_REQUIRE_GENERIC then return err_msg end\
7137
+ \t\tlocal module_path\
7138
+ \t\tif typeof(module) == "Instance" then\
7139
+ \t\t\tlocal ok_path, path = pcall(function()\
7140
+ \t\t\t\treturn module:GetFullName()\
7141
+ \t\t\tend)\
7142
+ \t\t\tif ok_path then module_path = path end\
7143
+ \t\tend\
7144
+ \t\ttask.wait(0.05)\
7145
+ \t\tlocal hist = __mcp_LogService:GetLogHistory()\
7146
+ \t\tfor i = #hist, history_start + 1, -1 do\
7147
+ \t\t\tlocal entry = hist[i]\
7148
+ \t\t\tif __mcp_is_actionable_require_log(entry) then\
7149
+ \t\t\t\treturn tostring(entry.message)\
7150
+ \t\t\tend\
7151
+ \t\tend\
7152
+ \t\tlocal prior = __mcp_prior_module_error(hist, module_path)\
7153
+ \t\tif prior then return prior end\
7154
+ \t\treturn err_msg\
7155
+ \tend\
7156
+ \tlocal function require(module)\
7157
+ \t\tlocal history_start = #__mcp_LogService:GetLogHistory()\
7158
+ \t\tlocal ok, value = pcall(__mcp_real_require, module)\
7159
+ \t\tif ok then return value end\
7160
+ \t\terror(__mcp_recover_require_error(value, history_start, module), 0)\
7161
+ \tend\
6957
7162
  \tlocal function __mcp_run()\
6958
7163
  {code}\
6959
7164
  \tend\
@@ -6964,15 +7169,20 @@ local function buildWrapper(code)
6964
7169
  \t\t-- Subtract LINE_OFFSET to get the user-relative number, then clamp.\
6965
7170
  \t\t-- Clamping matters for unclosed constructs ("local x = (") where the\
6966
7171
  \t\t-- parser keeps reading into wrapper postamble and reports a payload\
6967
- \t\t-- line past user EOF. Without clamping the message says "user_code:49"\
6968
- \t\t-- for one-line input, framing the wrapper as user code.\
7172
+ \t\t-- line past user EOF. Without clamping, that frames wrapper postamble\
7173
+ \t\t-- as user code.\
6969
7174
  \t\tlocal function __mcp_user_line(payload_n)\
6970
7175
  \t\t\tlocal user_n = payload_n - __mcp_LINE_OFFSET\
6971
7176
  \t\t\tif user_n < 1 then return "1" end\
6972
7177
  \t\t\tif user_n > __mcp_USER_LINES then return tostring(__mcp_USER_LINES) .. " (at end of input)" end\
6973
7178
  \t\t\treturn tostring(user_n)\
6974
7179
  \t\tend\
6975
- \t\ts = string.gsub(s, "__MCPExecLuauPayload:(%d+)", function(num)\
7180
+ \t\ts = string.gsub(s, "Workspace%.{payloadPattern}:(%d+)", function(num)\
7181
+ \t\t\tlocal n = tonumber(num)\
7182
+ \t\t\tif n then return "user_code:" .. __mcp_user_line(n) end\
7183
+ \t\t\treturn "user_code:" .. num\
7184
+ \t\tend)\
7185
+ \t\ts = string.gsub(s, "{payloadPattern}:(%d+)", function(num)\
6976
7186
  \t\t\tlocal n = tonumber(num)\
6977
7187
  \t\t\tif n then return "user_code:" .. __mcp_user_line(n) end\
6978
7188
  \t\t\treturn "user_code:" .. num\
@@ -7022,7 +7232,10 @@ end
7022
7232
  -- pulling the real compile-error diagnostic out of LogService — that error
7023
7233
  -- references the payload module's line number directly, and never passes
7024
7234
  -- through the IIFE's runtime wrapper.
7025
- local function remapPayloadLines(s, userLines)
7235
+ local function remapPayloadLines(s, userLines, payloadInstanceName)
7236
+ if payloadInstanceName == nil then
7237
+ payloadInstanceName = PAYLOAD_INSTANCE_NAME
7238
+ end
7026
7239
  -- Mirror of the Lua __mcp_remap inside the wrapper, for paths that
7027
7240
  -- don't pass through the IIFE (compile errors recovered from
7028
7241
  -- LogService, the immediate loadstring compileError surface). Same
@@ -7040,8 +7253,9 @@ local function remapPayloadLines(s, userLines)
7040
7253
  end
7041
7254
  return tostring(u)
7042
7255
  end
7256
+ local payloadPattern = luaPatternEscape(payloadInstanceName)
7043
7257
  local out = s
7044
- local a = string.gsub(out, "__MCPExecLuauPayload:(%d+)", function(num)
7258
+ local a = string.gsub(out, `Workspace%.{payloadPattern}:(%d+)`, function(num)
7045
7259
  local n = tonumber(num)
7046
7260
  if n ~= nil then
7047
7261
  return `user_code:{userLine(n)}`
@@ -7049,7 +7263,7 @@ local function remapPayloadLines(s, userLines)
7049
7263
  return `user_code:{num}`
7050
7264
  end)
7051
7265
  out = a
7052
- local b = string.gsub(out, '%[string "[^"]+"%]:(%d+)', function(num)
7266
+ local b = string.gsub(out, `{payloadPattern}:(%d+)`, function(num)
7053
7267
  local n = tonumber(num)
7054
7268
  if n ~= nil then
7055
7269
  return `user_code:{userLine(n)}`
@@ -7057,8 +7271,16 @@ local function remapPayloadLines(s, userLines)
7057
7271
  return `user_code:{num}`
7058
7272
  end)
7059
7273
  out = b
7060
- return out
7274
+ local c = string.gsub(out, '%[string "[^"]+"%]:(%d+)', function(num)
7275
+ local n = tonumber(num)
7276
+ if n ~= nil then
7277
+ return `user_code:{userLine(n)}`
7278
+ end
7279
+ return `user_code:{num}`
7280
+ end)
7281
+ return c
7061
7282
  end
7283
+ local recoverPayloadRequireError
7062
7284
  local function runViaModuleScript(wrapped, userLines)
7063
7285
  local m = Instance.new("ModuleScript")
7064
7286
  m.Name = PAYLOAD_INSTANCE_NAME
@@ -7078,26 +7300,11 @@ local function runViaModuleScript(wrapped, userLines)
7078
7300
  end)
7079
7301
  m:Destroy()
7080
7302
  if not okReq then
7081
- local errMsg = tostring(reqResult)
7082
- -- pcall(require, m) collapses parse/compile failures into the canned
7083
- -- engine string. The real diagnostic was emitted to LogService on the
7084
- -- next engine frame — give it ~50ms to land then scan backward.
7085
- if errMsg == "Requested module experienced an error while loading" then
7086
- task.wait(0.05)
7087
- local hist = LogService:GetLogHistory()
7088
- for i = #hist - 1, 0, -1 do
7089
- local e = hist[i + 1]
7090
- if e.messageType == Enum.MessageType.MessageError and string.sub(e.message, 1, #PAYLOAD_PATH_PREFIX) == PAYLOAD_PATH_PREFIX then
7091
- errMsg = e.message
7092
- break
7093
- end
7094
- end
7095
- end
7096
7303
  -- Compile errors reference the payload module's line number directly
7097
7304
  -- — remap + clamp to user-relative line numbers so `local x = 1 +`
7098
7305
  -- reports :1: instead of :23:, and reports the clamp annotation
7099
7306
  -- when the parser ran off the end of user code into wrapper code.
7100
- error(remapPayloadLines(errMsg, userLines), 0)
7307
+ error(recoverPayloadRequireError(reqResult, userLines, PAYLOAD_INSTANCE_NAME), 0)
7101
7308
  end
7102
7309
  return reqResult
7103
7310
  end
@@ -7124,6 +7331,44 @@ local function formatReturnValue(value)
7124
7331
  end
7125
7332
  return tostring(value)
7126
7333
  end
7334
+ function recoverPayloadRequireError(err, userLines, payloadInstanceName, historyStart)
7335
+ if payloadInstanceName == nil then
7336
+ payloadInstanceName = PAYLOAD_INSTANCE_NAME
7337
+ end
7338
+ if historyStart == nil then
7339
+ historyStart = 0
7340
+ end
7341
+ local errMsg = tostring(err)
7342
+ -- pcall(require, m) collapses parse/compile failures into the canned
7343
+ -- engine string. The real diagnostic is emitted to LogService on the
7344
+ -- next engine frame — give it ~50ms to land then scan backward.
7345
+ if errMsg == REQUIRE_GENERIC_ERROR then
7346
+ task.wait(0.05)
7347
+ local payloadPathPrefix = `Workspace.{payloadInstanceName}:`
7348
+ local hist = LogService:GetLogHistory()
7349
+ local start = math.max(0, historyStart)
7350
+ do
7351
+ local i = #hist - 1
7352
+ local _shouldIncrement = false
7353
+ while true do
7354
+ if _shouldIncrement then
7355
+ i -= 1
7356
+ else
7357
+ _shouldIncrement = true
7358
+ end
7359
+ if not (i >= start) then
7360
+ break
7361
+ end
7362
+ local e = hist[i + 1]
7363
+ if e.messageType == Enum.MessageType.MessageError and string.sub(e.message, 1, #payloadPathPrefix) == payloadPathPrefix then
7364
+ errMsg = e.message
7365
+ break
7366
+ end
7367
+ end
7368
+ end
7369
+ end
7370
+ return remapPayloadLines(errMsg, userLines, payloadInstanceName)
7371
+ end
7127
7372
  local function execute(code)
7128
7373
  if not (code ~= "" and code) or code == "" then
7129
7374
  return {
@@ -7177,12 +7422,17 @@ local function execute(code)
7177
7422
  }
7178
7423
  end
7179
7424
  return {
7425
+ buildWrapper = buildWrapper,
7426
+ countLines = countLines,
7180
7427
  execute = execute,
7428
+ formatReturnValue = formatReturnValue,
7429
+ recoverPayloadRequireError = recoverPayloadRequireError,
7430
+ remapPayloadLines = remapPayloadLines,
7181
7431
  }
7182
7432
  ]]></string>
7183
7433
  </Properties>
7184
7434
  </Item>
7185
- <Item class="ModuleScript" referent="21">
7435
+ <Item class="ModuleScript" referent="22">
7186
7436
  <Properties>
7187
7437
  <string name="Name">Recording</string>
7188
7438
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7212,7 +7462,7 @@ return {
7212
7462
  ]]></string>
7213
7463
  </Properties>
7214
7464
  </Item>
7215
- <Item class="ModuleScript" referent="22">
7465
+ <Item class="ModuleScript" referent="23">
7216
7466
  <Properties>
7217
7467
  <string name="Name">RenderMonitor</string>
7218
7468
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7280,12 +7530,12 @@ return {
7280
7530
  ]]></string>
7281
7531
  </Properties>
7282
7532
  </Item>
7283
- <Item class="ModuleScript" referent="23">
7533
+ <Item class="ModuleScript" referent="24">
7284
7534
  <Properties>
7285
7535
  <string name="Name">RuntimeLogBuffer</string>
7286
7536
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
7287
7537
  local TS = require(script.Parent.Parent.include.RuntimeLib)
7288
- -- Per-peer in-memory ring buffer for LogService.MessageOut events.
7538
+ -- Per-capture in-memory ring buffer for LogService.MessageOut events.
7289
7539
  -- Powers the get_runtime_logs MCP tool. Replaces the out-of-tree LogBuffer
7290
7540
  -- primitives + StringValue approach from chrrxs/roblox-mcp-primitives.
7291
7541
  --
@@ -7295,12 +7545,12 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
7295
7545
  -- DataModel. The buffer is bounded by a message-byte budget; oldest entries
7296
7546
  -- drop when over budget.
7297
7547
  --
7298
- -- Peer-tag caveat: returned entries reflect which peer's plugin CAPTURED the
7548
+ -- Capture caveat: returned entries reflect which plugin buffer CAPTURED the
7299
7549
  -- entry, NOT which peer's script originated the print. LogService reflects
7300
- -- prints across peers in Studio Play (a server print ends up in both the
7301
- -- server and client LogService:GetLogHistory()) and origin is empirically
7302
- -- undetectable from inside MessageOut. The MCP-side aggregator handles
7303
- -- cross-peer dedup via a 2s timestamp window.
7550
+ -- prints across peers in ordinary Studio Play (a server print can appear in
7551
+ -- server and client LogService:GetLogHistory()). The MCP-side aggregator
7552
+ -- exposes that as capturedBy, and only promotes it to origin peer in
7553
+ -- StudioTestService multiplayer sessions where peer attribution is reliable.
7304
7554
  local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
7305
7555
  local LogService = _services.LogService
7306
7556
  local RunService = _services.RunService
@@ -7364,7 +7614,7 @@ local function detectPeer()
7364
7614
  end
7365
7615
  return "client"
7366
7616
  end
7367
- local function query(opts, peer)
7617
+ local function query(opts, capturedBy)
7368
7618
  local _result
7369
7619
  if opts.since ~= nil then
7370
7620
  -- ▼ ReadonlyArray.filter ▼
@@ -7435,7 +7685,7 @@ local function query(opts, peer)
7435
7685
  end
7436
7686
  local last = if #entries > 0 then entries[#entries] else nil
7437
7687
  local _object = {
7438
- peer = peer,
7688
+ capturedBy = capturedBy,
7439
7689
  entries = result,
7440
7690
  totalDropped = totalDropped,
7441
7691
  }
@@ -7461,11 +7711,11 @@ return {
7461
7711
  ]]></string>
7462
7712
  </Properties>
7463
7713
  </Item>
7464
- <Item class="ModuleScript" referent="24">
7714
+ <Item class="ModuleScript" referent="25">
7465
7715
  <Properties>
7466
7716
  <string name="Name">State</string>
7467
7717
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
7468
- local CURRENT_VERSION = "2.15.1"
7718
+ local CURRENT_VERSION = "2.15.2"
7469
7719
  local PLUGIN_VARIANT = "inspector"
7470
7720
  local MAX_CONNECTIONS = 5
7471
7721
  local BASE_PORT = 58741
@@ -7559,7 +7809,7 @@ return {
7559
7809
  ]]></string>
7560
7810
  </Properties>
7561
7811
  </Item>
7562
- <Item class="ModuleScript" referent="25">
7812
+ <Item class="ModuleScript" referent="26">
7563
7813
  <Properties>
7564
7814
  <string name="Name">StopPlayMonitor</string>
7565
7815
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7704,7 +7954,7 @@ return {
7704
7954
  ]]></string>
7705
7955
  </Properties>
7706
7956
  </Item>
7707
- <Item class="ModuleScript" referent="26">
7957
+ <Item class="ModuleScript" referent="27">
7708
7958
  <Properties>
7709
7959
  <string name="Name">UI</string>
7710
7960
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -8475,7 +8725,7 @@ return {
8475
8725
  ]]></string>
8476
8726
  </Properties>
8477
8727
  </Item>
8478
- <Item class="ModuleScript" referent="27">
8728
+ <Item class="ModuleScript" referent="28">
8479
8729
  <Properties>
8480
8730
  <string name="Name">Utils</string>
8481
8731
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9005,11 +9255,11 @@ return {
9005
9255
  </Properties>
9006
9256
  </Item>
9007
9257
  </Item>
9008
- <Item class="Folder" referent="31">
9258
+ <Item class="Folder" referent="32">
9009
9259
  <Properties>
9010
9260
  <string name="Name">include</string>
9011
9261
  </Properties>
9012
- <Item class="ModuleScript" referent="28">
9262
+ <Item class="ModuleScript" referent="29">
9013
9263
  <Properties>
9014
9264
  <string name="Name">Promise</string>
9015
9265
  <string name="Source"><![CDATA[--[[
@@ -11083,7 +11333,7 @@ return Promise
11083
11333
  ]]></string>
11084
11334
  </Properties>
11085
11335
  </Item>
11086
- <Item class="ModuleScript" referent="29">
11336
+ <Item class="ModuleScript" referent="30">
11087
11337
  <Properties>
11088
11338
  <string name="Name">RuntimeLib</string>
11089
11339
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -11350,15 +11600,15 @@ return TS
11350
11600
  </Properties>
11351
11601
  </Item>
11352
11602
  </Item>
11353
- <Item class="Folder" referent="32">
11603
+ <Item class="Folder" referent="33">
11354
11604
  <Properties>
11355
11605
  <string name="Name">node_modules</string>
11356
11606
  </Properties>
11357
- <Item class="Folder" referent="33">
11607
+ <Item class="Folder" referent="34">
11358
11608
  <Properties>
11359
11609
  <string name="Name">@rbxts</string>
11360
11610
  </Properties>
11361
- <Item class="ModuleScript" referent="30">
11611
+ <Item class="ModuleScript" referent="31">
11362
11612
  <Properties>
11363
11613
  <string name="Name">services</string>
11364
11614
  <string name="Source"><![CDATA[return setmetatable({}, {