@chrrxs/robloxstudio-mcp-inspector 2.15.2 → 2.16.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.
- package/dist/index.js +1232 -45
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +174 -133
- package/studio-plugin/MCPPlugin.rbxmx +174 -133
- package/studio-plugin/src/modules/Communication.ts +4 -12
- package/studio-plugin/src/modules/EvalBridges.ts +91 -64
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +34 -6
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +2 -33
- package/studio-plugin/src/server/index.server.ts +27 -8
package/package.json
CHANGED
|
@@ -10,6 +10,9 @@ local State = TS.import(script, script, "modules", "State")
|
|
|
10
10
|
local UI = TS.import(script, script, "modules", "UI")
|
|
11
11
|
local Communication = TS.import(script, script, "modules", "Communication")
|
|
12
12
|
local ClientBroker = TS.import(script, script, "modules", "ClientBroker")
|
|
13
|
+
local _EvalBridges = TS.import(script, script, "modules", "EvalBridges")
|
|
14
|
+
local cleanupLegacyEditBridges = _EvalBridges.cleanupLegacyEditBridges
|
|
15
|
+
local ensureRuntimeBridgeInstalled = _EvalBridges.ensureRuntimeBridgeInstalled
|
|
13
16
|
local RuntimeLogBuffer = TS.import(script, script, "modules", "RuntimeLogBuffer")
|
|
14
17
|
local StopPlayMonitor = TS.import(script, script, "modules", "StopPlayMonitor")
|
|
15
18
|
local RenderMonitor = TS.import(script, script, "modules", "RenderMonitor")
|
|
@@ -30,13 +33,24 @@ local elements = UI.getElements()
|
|
|
30
33
|
local ICON_DISCONNECTED = "rbxassetid://125921838360800"
|
|
31
34
|
local ICON_CONNECTING = "rbxassetid://125921838360800"
|
|
32
35
|
local ICON_CONNECTED = "rbxassetid://125921838360800"
|
|
33
|
-
local
|
|
34
|
-
local
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
local TOOLBAR_REGISTRATION_DELAY_SECONDS = 1
|
|
37
|
+
local toolbarButtonRegistered = false
|
|
38
|
+
local function registerToolbarButton()
|
|
39
|
+
if toolbarButtonRegistered then
|
|
40
|
+
return nil
|
|
41
|
+
end
|
|
42
|
+
toolbarButtonRegistered = true
|
|
43
|
+
local toolbar = plugin:CreateToolbar("MCP Inspector")
|
|
44
|
+
local button = toolbar:CreateButton("MCP Inspector", "Connect to MCP Inspector (read-only) for AI Integration", ICON_DISCONNECTED)
|
|
45
|
+
UI.setToolbarButton(button, {
|
|
46
|
+
disconnected = ICON_DISCONNECTED,
|
|
47
|
+
connecting = ICON_CONNECTING,
|
|
48
|
+
connected = ICON_CONNECTED,
|
|
49
|
+
})
|
|
50
|
+
button.Click:Connect(function()
|
|
51
|
+
elements.screenGui.Enabled = not elements.screenGui.Enabled
|
|
52
|
+
end)
|
|
53
|
+
end
|
|
40
54
|
elements.connectButton.Activated:Connect(function()
|
|
41
55
|
local conn = State.getActiveConnection()
|
|
42
56
|
if conn and conn.isActive then
|
|
@@ -45,20 +59,26 @@ elements.connectButton.Activated:Connect(function()
|
|
|
45
59
|
Communication.activatePlugin(State.getActiveTabIndex())
|
|
46
60
|
end
|
|
47
61
|
end)
|
|
48
|
-
button.Click:Connect(function()
|
|
49
|
-
elements.screenGui.Enabled = not elements.screenGui.Enabled
|
|
50
|
-
end)
|
|
51
62
|
plugin.Unloading:Connect(function()
|
|
52
63
|
Communication.deactivateAll()
|
|
53
64
|
end)
|
|
54
65
|
UI.updateUIState()
|
|
55
66
|
Communication.checkForUpdates()
|
|
67
|
+
task.delay(TOOLBAR_REGISTRATION_DELAY_SECONDS, registerToolbarButton)
|
|
56
68
|
-- Auto-activate per peer. The boshyxd plugin only registers with MCP when the
|
|
57
69
|
-- user clicks Connect in its UI, but that UI is invisible in play DMs - so
|
|
58
70
|
-- play peers' plugin instances load without ever registering. Run after a
|
|
59
71
|
-- short delay so the UI/State have a chance to initialize first.
|
|
60
72
|
task.delay(2, function()
|
|
61
73
|
local role = ClientBroker.forkRole()
|
|
74
|
+
if role == "edit" then
|
|
75
|
+
cleanupLegacyEditBridges()
|
|
76
|
+
else
|
|
77
|
+
local result = ensureRuntimeBridgeInstalled()
|
|
78
|
+
if not result.installed then
|
|
79
|
+
warn(`[MCPPlugin] Runtime eval bridge install failed: {result.error}`)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
62
82
|
if role == "edit" or role == "server" then
|
|
63
83
|
pcall(function()
|
|
64
84
|
local idx = State.getActiveTabIndex()
|
|
@@ -552,7 +572,7 @@ local ServerStorage = _services.ServerStorage
|
|
|
552
572
|
local State = TS.import(script, script.Parent, "State")
|
|
553
573
|
local Utils = TS.import(script, script.Parent, "Utils")
|
|
554
574
|
local UI = TS.import(script, script.Parent, "UI")
|
|
555
|
-
local
|
|
575
|
+
local cleanupLegacyEditBridges = TS.import(script, script.Parent, "EvalBridges").cleanupLegacyEditBridges
|
|
556
576
|
local QueryHandlers = TS.import(script, script.Parent, "handlers", "QueryHandlers")
|
|
557
577
|
local PropertyHandlers = TS.import(script, script.Parent, "handlers", "PropertyHandlers")
|
|
558
578
|
local InstanceHandlers = TS.import(script, script.Parent, "handlers", "InstanceHandlers")
|
|
@@ -1056,18 +1076,10 @@ local function activatePlugin(connIndex)
|
|
|
1056
1076
|
-- Initial /ready; pollForRequests will also re-fire ready if the server
|
|
1057
1077
|
-- later reports knownInstance=false (process restart, etc).
|
|
1058
1078
|
sendReady(conn)
|
|
1059
|
-
--
|
|
1060
|
-
--
|
|
1061
|
-
-- clones them into the play DMs and eval_*_runtime works with no setup
|
|
1062
|
-
-- roundtrip. Only the edit DM installs; play DMs already have the cloned
|
|
1063
|
-
-- copies. Idempotent, so reconnects don't re-dirty the place.
|
|
1079
|
+
-- Remove legacy edit-mode eval bridge scripts from older plugin builds.
|
|
1080
|
+
-- Current bridges are created only in running play DataModels.
|
|
1064
1081
|
if not RunService:IsRunning() then
|
|
1065
|
-
task.spawn(
|
|
1066
|
-
local result = ensureBridgesInstalled()
|
|
1067
|
-
if not result.installed then
|
|
1068
|
-
warn(`[MCPPlugin] Eval bridge install failed: {result.error}`)
|
|
1069
|
-
end
|
|
1070
|
-
end)
|
|
1082
|
+
task.spawn(cleanupLegacyEditBridges)
|
|
1071
1083
|
end
|
|
1072
1084
|
-- Watch for game.Name updates so a stale "Place1" captured at first
|
|
1073
1085
|
-- /ready gets refreshed once Studio settles on the real DM name.
|
|
@@ -1188,29 +1200,15 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
|
1188
1200
|
-- ServerScriptService.LoadStringEnabled, so eval_server_runtime works even
|
|
1189
1201
|
-- when LoadStringEnabled=false (the default in fresh places).
|
|
1190
1202
|
--
|
|
1191
|
-
-- Lifecycle:
|
|
1192
|
-
--
|
|
1193
|
-
--
|
|
1194
|
-
--
|
|
1195
|
-
--
|
|
1196
|
-
-- DM after a playtest ends (rather than cleaning up) so that a playtest the
|
|
1197
|
-
-- dev starts MANUALLY via the Studio Play button — not the MCP start_playtest
|
|
1198
|
-
-- tool — also gets the bridges cloned in. This is intentionally a little
|
|
1199
|
-
-- intrusive (two helper scripts visible in Explorer) in exchange for a
|
|
1200
|
-
-- zero-roundtrip eval_*_runtime experience for devs working 1:1 with an agent.
|
|
1201
|
-
--
|
|
1202
|
-
-- Archivable handling: ExecutePlayModeAsync's deep-clone SKIPS instances
|
|
1203
|
-
-- with Archivable=false (verified empirically in v2.9.0 testing - bridges
|
|
1204
|
-
-- never reached the play DMs because we'd set them to false). We now keep
|
|
1205
|
-
-- Archivable=true so the clone works, and rely on cleanupBridges() to
|
|
1206
|
-
-- remove the scripts from the edit DM when the test ends. The only failure
|
|
1207
|
-
-- mode is the user saving DURING an active playtest, which would persist
|
|
1208
|
-
-- the bridges to the .rbxl - that's a no-op next session because
|
|
1209
|
-
-- installBridges() always calls cleanupBridges() first to clear stale
|
|
1210
|
-
-- instances. The RemoteFunction/BindableFunction that the bridge scripts
|
|
1211
|
-
-- CREATE at runtime stay Archivable=false (they're runtime-only and should
|
|
1212
|
-
-- never appear in a save).
|
|
1203
|
+
-- Lifecycle: bridge scripts are created only in running play DataModels.
|
|
1204
|
+
-- The server plugin peer creates the Script in runtime ServerScriptService;
|
|
1205
|
+
-- each client plugin peer creates its LocalScript in that client's
|
|
1206
|
+
-- PlayerScripts. Nothing is installed into the edit DataModel anymore.
|
|
1207
|
+
-- Runtime-created scripts disappear naturally when the playtest stops.
|
|
1213
1208
|
local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
1209
|
+
local Players = _services.Players
|
|
1210
|
+
local ReplicatedStorage = _services.ReplicatedStorage
|
|
1211
|
+
local RunService = _services.RunService
|
|
1214
1212
|
local ServerScriptService = _services.ServerScriptService
|
|
1215
1213
|
local StarterPlayer = _services.StarterPlayer
|
|
1216
1214
|
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
@@ -1283,12 +1281,10 @@ bf.OnInvoke = function(payload)\
|
|
|
1283
1281
|
end\
|
|
1284
1282
|
`
|
|
1285
1283
|
-- Stamp written onto each installed bridge Script so we can tell whether the
|
|
1286
|
-
-- bridge currently in the DM was produced by THIS plugin build.
|
|
1287
|
-
-- hash of the actual bridge source plus the plugin version, so ANY
|
|
1288
|
-
-- the source (or a version bump) yields a new stamp
|
|
1289
|
-
--
|
|
1290
|
-
-- keeping a stale bridge that happens to still be present (e.g. one saved into
|
|
1291
|
-
-- the .rbxl from an older build).
|
|
1284
|
+
-- runtime bridge currently in the play DM was produced by THIS plugin build.
|
|
1285
|
+
-- It's a djb2 hash of the actual bridge source plus the plugin version, so ANY
|
|
1286
|
+
-- change to the source (or a version bump) yields a new stamp and triggers a
|
|
1287
|
+
-- runtime refresh instead of keeping a stale bridge.
|
|
1292
1288
|
local STAMP_ATTR = "__MCPBridgeStamp"
|
|
1293
1289
|
local function computeBridgeStamp()
|
|
1294
1290
|
local combined = `{SERVER_BRIDGE_SOURCE}|{CLIENT_BRIDGE_SOURCE}`
|
|
@@ -1296,9 +1292,9 @@ local function computeBridgeStamp()
|
|
|
1296
1292
|
for i = 1, #combined do
|
|
1297
1293
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1298
1294
|
end
|
|
1299
|
-
-- "2.
|
|
1295
|
+
-- "2.16.0" is replaced with the package version at package time
|
|
1300
1296
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1301
|
-
return `{tostring(h)}-2.
|
|
1297
|
+
return `{tostring(h)}-2.16.0`
|
|
1302
1298
|
end
|
|
1303
1299
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1304
1300
|
local function setSource(scriptInst, source)
|
|
@@ -1314,15 +1310,26 @@ local function setSource(scriptInst, source)
|
|
|
1314
1310
|
scriptInst.Source = source
|
|
1315
1311
|
end
|
|
1316
1312
|
end
|
|
1317
|
-
local function
|
|
1313
|
+
local function findLegacyEditBridges()
|
|
1318
1314
|
local sps = getStarterPlayerScripts()
|
|
1319
1315
|
return {
|
|
1320
1316
|
server = ServerScriptService:FindFirstChild(SERVER_SCRIPT_NAME),
|
|
1321
1317
|
client = if sps then sps:FindFirstChild(CLIENT_SCRIPT_NAME) else nil,
|
|
1322
1318
|
}
|
|
1323
1319
|
end
|
|
1324
|
-
local function
|
|
1325
|
-
local
|
|
1320
|
+
local function destroyIfPresent(parent, name)
|
|
1321
|
+
local existing = parent:FindFirstChild(name)
|
|
1322
|
+
if existing then
|
|
1323
|
+
pcall(function()
|
|
1324
|
+
return existing:Destroy()
|
|
1325
|
+
end)
|
|
1326
|
+
end
|
|
1327
|
+
end
|
|
1328
|
+
local function cleanupLegacyEditBridges()
|
|
1329
|
+
if RunService:IsRunning() then
|
|
1330
|
+
return nil
|
|
1331
|
+
end
|
|
1332
|
+
local _binding = findLegacyEditBridges()
|
|
1326
1333
|
local server = _binding.server
|
|
1327
1334
|
local client = _binding.client
|
|
1328
1335
|
if server then
|
|
@@ -1336,54 +1343,79 @@ local function cleanupBridges()
|
|
|
1336
1343
|
end)
|
|
1337
1344
|
end
|
|
1338
1345
|
end
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
local
|
|
1345
|
-
local
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
-- plugin, or one persisted in the saved place), so force a refresh.
|
|
1353
|
-
local sStamp = server:GetAttribute(STAMP_ATTR)
|
|
1354
|
-
local cStamp = client:GetAttribute(STAMP_ATTR)
|
|
1355
|
-
if sStamp == BRIDGE_STAMP and cStamp == BRIDGE_STAMP then
|
|
1356
|
-
return {
|
|
1357
|
-
installed = true,
|
|
1358
|
-
}
|
|
1359
|
-
end
|
|
1346
|
+
local function serverRuntimeBridgeReady()
|
|
1347
|
+
local scriptInst = ServerScriptService:FindFirstChild(SERVER_SCRIPT_NAME)
|
|
1348
|
+
local bindable = ServerScriptService:FindFirstChild(BRIDGE_NAMES.serverLocal)
|
|
1349
|
+
return scriptInst ~= nil and scriptInst:GetAttribute(STAMP_ATTR) == BRIDGE_STAMP and bindable ~= nil and bindable:IsA("BindableFunction")
|
|
1350
|
+
end
|
|
1351
|
+
local function getPlayerScripts()
|
|
1352
|
+
local localPlayer = Players.LocalPlayer
|
|
1353
|
+
if not localPlayer then
|
|
1354
|
+
return nil
|
|
1355
|
+
end
|
|
1356
|
+
local playerScripts = localPlayer:FindFirstChild("PlayerScripts")
|
|
1357
|
+
if not playerScripts then
|
|
1358
|
+
playerScripts = localPlayer:WaitForChild("PlayerScripts", 5)
|
|
1360
1359
|
end
|
|
1361
|
-
return
|
|
1360
|
+
return playerScripts
|
|
1362
1361
|
end
|
|
1363
|
-
function
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1362
|
+
local function clientRuntimeBridgeReady()
|
|
1363
|
+
local playerScripts = getPlayerScripts()
|
|
1364
|
+
if not playerScripts then
|
|
1365
|
+
return false
|
|
1366
|
+
end
|
|
1367
|
+
local scriptInst = playerScripts:FindFirstChild(CLIENT_SCRIPT_NAME)
|
|
1368
|
+
local bindable = ReplicatedStorage:FindFirstChild(BRIDGE_NAMES.clientLocal)
|
|
1369
|
+
return scriptInst ~= nil and scriptInst:GetAttribute(STAMP_ATTR) == BRIDGE_STAMP and bindable ~= nil and bindable:IsA("BindableFunction")
|
|
1370
|
+
end
|
|
1371
|
+
local function installServerRuntimeBridge()
|
|
1372
|
+
if serverRuntimeBridgeReady() then
|
|
1373
|
+
return {
|
|
1374
|
+
installed = true,
|
|
1375
|
+
}
|
|
1376
|
+
end
|
|
1369
1377
|
local ok, err = pcall(function()
|
|
1378
|
+
destroyIfPresent(ServerScriptService, SERVER_SCRIPT_NAME)
|
|
1379
|
+
destroyIfPresent(ServerScriptService, BRIDGE_NAMES.serverLocal)
|
|
1370
1380
|
local serverScript = Instance.new("Script")
|
|
1371
1381
|
serverScript.Name = SERVER_SCRIPT_NAME
|
|
1372
|
-
|
|
1373
|
-
-- script. cleanupBridges() removes it from the edit DM when the
|
|
1374
|
-
-- playtest ends.
|
|
1382
|
+
serverScript.Archivable = false
|
|
1375
1383
|
setSource(serverScript, SERVER_BRIDGE_SOURCE)
|
|
1376
1384
|
serverScript:SetAttribute(STAMP_ATTR, BRIDGE_STAMP)
|
|
1377
1385
|
serverScript.Parent = ServerScriptService
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1386
|
+
end)
|
|
1387
|
+
if not ok then
|
|
1388
|
+
return {
|
|
1389
|
+
installed = false,
|
|
1390
|
+
error = tostring(err),
|
|
1391
|
+
}
|
|
1392
|
+
end
|
|
1393
|
+
return {
|
|
1394
|
+
installed = true,
|
|
1395
|
+
}
|
|
1396
|
+
end
|
|
1397
|
+
local function installClientRuntimeBridge()
|
|
1398
|
+
if clientRuntimeBridgeReady() then
|
|
1399
|
+
return {
|
|
1400
|
+
installed = true,
|
|
1401
|
+
}
|
|
1402
|
+
end
|
|
1403
|
+
local playerScripts = getPlayerScripts()
|
|
1404
|
+
if not playerScripts then
|
|
1405
|
+
return {
|
|
1406
|
+
installed = false,
|
|
1407
|
+
error = "Players.LocalPlayer.PlayerScripts not found - cannot install client eval bridge",
|
|
1408
|
+
}
|
|
1409
|
+
end
|
|
1410
|
+
local ok, err = pcall(function()
|
|
1411
|
+
destroyIfPresent(playerScripts, CLIENT_SCRIPT_NAME)
|
|
1412
|
+
destroyIfPresent(ReplicatedStorage, BRIDGE_NAMES.clientLocal)
|
|
1382
1413
|
local clientScript = Instance.new("LocalScript")
|
|
1383
1414
|
clientScript.Name = CLIENT_SCRIPT_NAME
|
|
1415
|
+
clientScript.Archivable = false
|
|
1384
1416
|
setSource(clientScript, CLIENT_BRIDGE_SOURCE)
|
|
1385
1417
|
clientScript:SetAttribute(STAMP_ATTR, BRIDGE_STAMP)
|
|
1386
|
-
clientScript.Parent =
|
|
1418
|
+
clientScript.Parent = playerScripts
|
|
1387
1419
|
end)
|
|
1388
1420
|
if not ok then
|
|
1389
1421
|
return {
|
|
@@ -1395,10 +1427,21 @@ function installBridges()
|
|
|
1395
1427
|
installed = true,
|
|
1396
1428
|
}
|
|
1397
1429
|
end
|
|
1430
|
+
local function ensureRuntimeBridgeInstalled()
|
|
1431
|
+
if not RunService:IsRunning() then
|
|
1432
|
+
return {
|
|
1433
|
+
installed = false,
|
|
1434
|
+
error = "Eval bridges are installed only in running play DataModels",
|
|
1435
|
+
}
|
|
1436
|
+
end
|
|
1437
|
+
if RunService:IsServer() then
|
|
1438
|
+
return installServerRuntimeBridge()
|
|
1439
|
+
end
|
|
1440
|
+
return installClientRuntimeBridge()
|
|
1441
|
+
end
|
|
1398
1442
|
return {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
installBridges = installBridges,
|
|
1443
|
+
cleanupLegacyEditBridges = cleanupLegacyEditBridges,
|
|
1444
|
+
ensureRuntimeBridgeInstalled = ensureRuntimeBridgeInstalled,
|
|
1402
1445
|
BRIDGE_NAMES = BRIDGE_NAMES,
|
|
1403
1446
|
}
|
|
1404
1447
|
]]></string>
|
|
@@ -2484,9 +2527,27 @@ local LogService = _services.LogService
|
|
|
2484
2527
|
local ReplicatedStorage = _services.ReplicatedStorage
|
|
2485
2528
|
local RunService = _services.RunService
|
|
2486
2529
|
local ServerScriptService = _services.ServerScriptService
|
|
2487
|
-
local
|
|
2530
|
+
local _EvalBridges = TS.import(script, script.Parent.Parent, "EvalBridges")
|
|
2531
|
+
local BRIDGE_NAMES = _EvalBridges.BRIDGE_NAMES
|
|
2532
|
+
local ensureRuntimeBridgeInstalled = _EvalBridges.ensureRuntimeBridgeInstalled
|
|
2488
2533
|
local LuauExec = TS.import(script, script.Parent.Parent, "LuauExec")
|
|
2489
2534
|
local PAYLOAD_INSTANCE_NAME = "__MCPEvalPayload"
|
|
2535
|
+
local function findBridge(config)
|
|
2536
|
+
local bridge = config.service:FindFirstChild(config.bridgeName)
|
|
2537
|
+
return if bridge and bridge:IsA("BindableFunction") then bridge else nil
|
|
2538
|
+
end
|
|
2539
|
+
local function waitForBridge(config, timeoutSec)
|
|
2540
|
+
if timeoutSec == nil then
|
|
2541
|
+
timeoutSec = 2
|
|
2542
|
+
end
|
|
2543
|
+
local deadline = tick() + timeoutSec
|
|
2544
|
+
local bridge = findBridge(config)
|
|
2545
|
+
while not bridge and tick() < deadline do
|
|
2546
|
+
task.wait(0.05)
|
|
2547
|
+
bridge = findBridge(config)
|
|
2548
|
+
end
|
|
2549
|
+
return bridge
|
|
2550
|
+
end
|
|
2490
2551
|
local function getBridgeConfig()
|
|
2491
2552
|
if not RunService:IsRunning() then
|
|
2492
2553
|
return {
|
|
@@ -2497,13 +2558,13 @@ local function getBridgeConfig()
|
|
|
2497
2558
|
return {
|
|
2498
2559
|
service = ServerScriptService,
|
|
2499
2560
|
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
|
|
2561
|
+
missingError = "ServerEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically in the runtime server peer, including for manually-started playtests.",
|
|
2501
2562
|
}
|
|
2502
2563
|
end
|
|
2503
2564
|
return {
|
|
2504
2565
|
service = ReplicatedStorage,
|
|
2505
2566
|
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
|
|
2567
|
+
missingError = "ClientEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically in the runtime client peer, including for manually-started playtests.",
|
|
2507
2568
|
}
|
|
2508
2569
|
end
|
|
2509
2570
|
local function evalRuntime(requestData)
|
|
@@ -2520,11 +2581,21 @@ local function evalRuntime(requestData)
|
|
|
2520
2581
|
error = config.error,
|
|
2521
2582
|
}
|
|
2522
2583
|
end
|
|
2523
|
-
local bridge =
|
|
2524
|
-
if not bridge
|
|
2584
|
+
local bridge = findBridge(config)
|
|
2585
|
+
if not bridge then
|
|
2586
|
+
local install = ensureRuntimeBridgeInstalled()
|
|
2587
|
+
if not install.installed then
|
|
2588
|
+
return {
|
|
2589
|
+
bridge = "missing",
|
|
2590
|
+
error = `{config.missingError} Runtime bridge install failed: {install.error}`,
|
|
2591
|
+
}
|
|
2592
|
+
end
|
|
2593
|
+
bridge = waitForBridge(config)
|
|
2594
|
+
end
|
|
2595
|
+
if not bridge then
|
|
2525
2596
|
return {
|
|
2526
2597
|
bridge = "missing",
|
|
2527
|
-
error = config.missingError
|
|
2598
|
+
error = `{config.missingError} Runtime bridge was installed but did not become ready.`,
|
|
2528
2599
|
}
|
|
2529
2600
|
end
|
|
2530
2601
|
local m = Instance.new("ModuleScript")
|
|
@@ -6375,9 +6446,6 @@ local HttpService = _services.HttpService
|
|
|
6375
6446
|
local LogService = _services.LogService
|
|
6376
6447
|
local Players = _services.Players
|
|
6377
6448
|
local RunService = _services.RunService
|
|
6378
|
-
local _EvalBridges = TS.import(script, script.Parent.Parent, "EvalBridges")
|
|
6379
|
-
local installBridges = _EvalBridges.installBridges
|
|
6380
|
-
local ensureBridgesInstalled = _EvalBridges.ensureBridgesInstalled
|
|
6381
6449
|
local StopPlayMonitor = TS.import(script, script.Parent.Parent, "StopPlayMonitor")
|
|
6382
6450
|
local StudioTestService = game:GetService("StudioTestService")
|
|
6383
6451
|
local ServerScriptService = game:GetService("ServerScriptService")
|
|
@@ -6564,9 +6632,8 @@ local function startPlaytest(requestData)
|
|
|
6564
6632
|
logConnection = nil
|
|
6565
6633
|
end
|
|
6566
6634
|
cleanupStopListener()
|
|
6567
|
-
--
|
|
6568
|
-
--
|
|
6569
|
-
-- EvalBridges.ts lifecycle comment.
|
|
6635
|
+
-- Runtime eval bridges are created by the play server/client plugin
|
|
6636
|
+
-- peers and disappear with the play DataModels.
|
|
6570
6637
|
end
|
|
6571
6638
|
if testRunning then
|
|
6572
6639
|
return {
|
|
@@ -6609,14 +6676,6 @@ local function startPlaytest(requestData)
|
|
|
6609
6676
|
if not injected then
|
|
6610
6677
|
warn(`[MCP] Failed to inject stop listener: {injErr}`)
|
|
6611
6678
|
end
|
|
6612
|
-
-- Force-refresh the game-VM eval bridges (ServerEvalBridge + ClientEvalBridge)
|
|
6613
|
-
-- right before cloning so the play DMs get the current source. They also
|
|
6614
|
-
-- live permanently in the edit DM (installed on connect) so manually-started
|
|
6615
|
-
-- playtests get them too; here we just ensure they're fresh.
|
|
6616
|
-
local bridgeInstall = installBridges()
|
|
6617
|
-
if not bridgeInstall.installed then
|
|
6618
|
-
warn(`[MCP] Eval bridge install failed: {bridgeInstall.error}`)
|
|
6619
|
-
end
|
|
6620
6679
|
task.spawn(function()
|
|
6621
6680
|
local ok, result = pcall(function()
|
|
6622
6681
|
if mode == "play" then
|
|
@@ -6635,21 +6694,11 @@ local function startPlaytest(requestData)
|
|
|
6635
6694
|
end
|
|
6636
6695
|
testRunning = false
|
|
6637
6696
|
cleanupStopListener()
|
|
6638
|
-
-- Eval bridges persist in the edit DM (see EvalBridges.ts) — do not
|
|
6639
|
-
-- clean up here, so the next manual playtest still gets them.
|
|
6640
|
-
ensureBridgesInstalled()
|
|
6641
6697
|
end)
|
|
6642
6698
|
local response = {
|
|
6643
6699
|
success = true,
|
|
6644
6700
|
message = `Playtest started in {mode} mode.`,
|
|
6645
6701
|
}
|
|
6646
|
-
-- Only mention eval bridges when they failed — when they're fine, the
|
|
6647
|
-
-- detail is noise. eval_server_runtime / eval_client_runtime will surface
|
|
6648
|
-
-- their own clear errors if the caller tries to use them after a failed
|
|
6649
|
-
-- install.
|
|
6650
|
-
if not bridgeInstall.installed then
|
|
6651
|
-
response.evalBridgesError = bridgeInstall.error
|
|
6652
|
-
end
|
|
6653
6702
|
return response
|
|
6654
6703
|
end
|
|
6655
6704
|
local function stopPlaytest(_requestData)
|
|
@@ -6744,10 +6793,6 @@ local function multiplayerTestStart(requestData)
|
|
|
6744
6793
|
end
|
|
6745
6794
|
local testArgs = if requestData.testArgs ~= nil then requestData.testArgs else {}
|
|
6746
6795
|
local testId = HttpService:GenerateGUID(false)
|
|
6747
|
-
local bridgeInstall = installBridges()
|
|
6748
|
-
if not bridgeInstall.installed then
|
|
6749
|
-
warn(`[MCP] Eval bridge install failed: {bridgeInstall.error}`)
|
|
6750
|
-
end
|
|
6751
6796
|
multiplayerState = {
|
|
6752
6797
|
phase = "starting",
|
|
6753
6798
|
testId = testId,
|
|
@@ -6771,7 +6816,6 @@ local function multiplayerTestStart(requestData)
|
|
|
6771
6816
|
multiplayerState.result = nil
|
|
6772
6817
|
multiplayerState.error = tostring(result)
|
|
6773
6818
|
end
|
|
6774
|
-
ensureBridgesInstalled()
|
|
6775
6819
|
end)
|
|
6776
6820
|
local response = {
|
|
6777
6821
|
success = true,
|
|
@@ -6781,9 +6825,6 @@ local function multiplayerTestStart(requestData)
|
|
|
6781
6825
|
numPlayers = numPlayers,
|
|
6782
6826
|
testArgs = testArgs,
|
|
6783
6827
|
}
|
|
6784
|
-
if not bridgeInstall.installed then
|
|
6785
|
-
response.evalBridgesError = bridgeInstall.error
|
|
6786
|
-
end
|
|
6787
6828
|
return response
|
|
6788
6829
|
end
|
|
6789
6830
|
local function multiplayerTestState(_requestData)
|
|
@@ -7715,7 +7756,7 @@ return {
|
|
|
7715
7756
|
<Properties>
|
|
7716
7757
|
<string name="Name">State</string>
|
|
7717
7758
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
7718
|
-
local CURRENT_VERSION = "2.
|
|
7759
|
+
local CURRENT_VERSION = "2.16.0"
|
|
7719
7760
|
local PLUGIN_VARIANT = "inspector"
|
|
7720
7761
|
local MAX_CONNECTIONS = 5
|
|
7721
7762
|
local BASE_PORT = 58741
|