@chrrxs/robloxstudio-mcp 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrrxs/robloxstudio-mcp",
3
- "version": "2.15.2",
3
+ "version": "2.16.0",
4
4
  "description": "MCP server for testing, debugging, and controlling Roblox Studio from AI coding tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -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 toolbar = plugin:CreateToolbar("MCP Inspector")
34
- local button = toolbar:CreateButton("MCP Inspector", "Connect to MCP Inspector (read-only) for AI Integration", ICON_DISCONNECTED)
35
- UI.setToolbarButton(button, {
36
- disconnected = ICON_DISCONNECTED,
37
- connecting = ICON_CONNECTING,
38
- connected = ICON_CONNECTED,
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 ensureBridgesInstalled = TS.import(script, script.Parent, "EvalBridges").ensureBridgesInstalled
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
- -- Keep the eval bridges present in the edit DM so that ANY playtest —
1060
- -- including one the dev starts manually via the Studio Play button —
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(function()
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: the bridges live PERMANENTLY in the edit DM. Communication
1192
- -- installs them (ensureBridgesInstalled) when the plugin connects in edit,
1193
- -- and TestHandlers.startPlaytest force-refreshes them right before
1194
- -- ExecutePlayModeAsync. ExecutePlayModeAsync clones the DataModel into the
1195
- -- play DMs, so the scripts come along and run there. We keep them in the edit
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. It's a djb2
1287
- -- hash of the actual bridge source plus the plugin version, so ANY change to
1288
- -- the source (or a version bump) yields a new stamp which makes
1289
- -- ensureBridgesInstalled() force a refresh on the next plugin load instead of
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.15.2" is replaced with the package version at package time
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.15.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 findBridges()
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 cleanupBridges()
1325
- local _binding = findBridges()
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
- -- Idempotent variant: install only if the bridge scripts aren't already
1340
- -- present in the edit DM. Used to keep the bridges always available (so a
1341
- -- playtest the dev starts manually — not via the MCP start_playtest tool —
1342
- -- still clones them into the play DMs). Cheap no-op when already installed,
1343
- -- which avoids re-dirtying the place on every plugin reconnect.
1344
- local installBridges
1345
- local function ensureBridgesInstalled()
1346
- local _binding = findBridges()
1347
- local server = _binding.server
1348
- local client = _binding.client
1349
- if server and client then
1350
- -- Both present — but only skip the reinstall if they were produced by
1351
- -- THIS build. A mismatched/absent stamp means a stale bridge (older
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 installBridges()
1360
+ return playerScripts
1362
1361
  end
1363
- function installBridges()
1364
- -- Defensive: clear any stale bridges from a prior unclean exit before
1365
- -- inserting fresh. The injected script also self-cleans its
1366
- -- ReplicatedStorage/ServerScriptService children at startup, but the
1367
- -- containing Script/LocalScript objects themselves we must clear here.
1368
- cleanupBridges()
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
- -- Archivable=true so ExecutePlayModeAsync's deep-clone includes the
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
- local sps = getStarterPlayerScripts()
1379
- if not sps then
1380
- error("StarterPlayer.StarterPlayerScripts not found - cannot install client eval bridge")
1381
- end
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 = sps
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
- cleanupBridges = cleanupBridges,
1400
- ensureBridgesInstalled = ensureBridgesInstalled,
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 BRIDGE_NAMES = TS.import(script, script.Parent.Parent, "EvalBridges").BRIDGE_NAMES
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 (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.",
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 (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.",
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 = config.service:FindFirstChild(config.bridgeName)
2524
- if not bridge or not bridge:IsA("BindableFunction") then
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
- -- Note: eval bridges are intentionally NOT cleaned up they live
6568
- -- permanently in the edit DM so manual playtests also get them. See
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.15.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