@chrrxs/robloxstudio-mcp 2.8.0 → 2.9.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 +3847 -3555
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +272 -53
- package/studio-plugin/MCPPlugin.rbxmx +272 -53
- package/studio-plugin/src/modules/ClientBroker.ts +1 -5
- package/studio-plugin/src/modules/EvalBridges.ts +203 -0
- package/studio-plugin/src/modules/UI.ts +3 -1
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +41 -21
|
@@ -154,7 +154,6 @@ local function setupClientBroker()
|
|
|
154
154
|
}
|
|
155
155
|
return _arg0
|
|
156
156
|
end
|
|
157
|
-
print("[MCPFork] client broker ready")
|
|
158
157
|
end
|
|
159
158
|
local proxyByPlayer = {}
|
|
160
159
|
local function pollProxy(proxyId, player, rf)
|
|
@@ -238,7 +237,6 @@ local function registerProxy(player, rf)
|
|
|
238
237
|
role = assigned,
|
|
239
238
|
}
|
|
240
239
|
proxyByPlayer[_player_1] = _arg1
|
|
241
|
-
print(`[MCPFork] proxy {assigned} -> {player.Name}`)
|
|
242
240
|
task.spawn(pollProxy, proxyId, player, rf)
|
|
243
241
|
end
|
|
244
242
|
local function startEditProxyLoop()
|
|
@@ -246,13 +244,12 @@ local function startEditProxyLoop()
|
|
|
246
244
|
local proxyId = HttpService:GenerateGUID(false)
|
|
247
245
|
local ok, res = postJson("/ready", {
|
|
248
246
|
instanceId = proxyId,
|
|
249
|
-
role = "edit",
|
|
247
|
+
role = "edit-proxy",
|
|
250
248
|
})
|
|
251
249
|
if not ok or not res or not res.Success then
|
|
252
250
|
warn("[MCPFork] edit-proxy register failed")
|
|
253
251
|
return nil
|
|
254
252
|
end
|
|
255
|
-
print("[MCPFork] edit-proxy ready (stop-playtest interceptor)")
|
|
256
253
|
while true do
|
|
257
254
|
local okPoll, pollRes = pcall(function()
|
|
258
255
|
return HttpService:RequestAsync({
|
|
@@ -323,7 +320,6 @@ local function setupServerBroker()
|
|
|
323
320
|
table.clear(proxyByPlayer)
|
|
324
321
|
end)
|
|
325
322
|
startEditProxyLoop()
|
|
326
|
-
print("[MCPFork] server broker ready")
|
|
327
323
|
end
|
|
328
324
|
return {
|
|
329
325
|
MCP_URL = MCP_URL,
|
|
@@ -774,11 +770,227 @@ return {
|
|
|
774
770
|
]]></string>
|
|
775
771
|
</Properties>
|
|
776
772
|
</Item>
|
|
777
|
-
<Item class="
|
|
773
|
+
<Item class="ModuleScript" referent="4">
|
|
774
|
+
<Properties>
|
|
775
|
+
<string name="Name">EvalBridges</string>
|
|
776
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
777
|
+
local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
778
|
+
-- Game-VM eval bridges, ported from chrrxs/roblox-mcp-primitives.
|
|
779
|
+
--
|
|
780
|
+
-- Our standard `execute_luau target=server/client-N` runs in the plugin VM
|
|
781
|
+
-- with a fresh ModuleScript per call. That gives a clean sandbox but means
|
|
782
|
+
-- `require(SomeModule)` returns a fresh copy, not the one the running game
|
|
783
|
+
-- scripts hold. So runtime-mutated module state is invisible to probes.
|
|
784
|
+
--
|
|
785
|
+
-- These bridges fix that by living inside the user's game scripts:
|
|
786
|
+
-- - Server: a Script in ServerScriptService that creates a BindableFunction
|
|
787
|
+
-- (for our server-peer plugin to invoke directly) plus a RemoteFunction
|
|
788
|
+
-- (kept for parity with the upstream primitive's client-callable shape).
|
|
789
|
+
-- - Client: a LocalScript in StarterPlayer.StarterPlayerScripts that
|
|
790
|
+
-- creates a BindableFunction. Plugin invokes it with a fresh ModuleScript
|
|
791
|
+
-- payload; require() runs inside the LocalScript VM so it shares the
|
|
792
|
+
-- game's require cache.
|
|
793
|
+
--
|
|
794
|
+
-- Lifecycle: TestHandlers.startPlaytest inserts both scripts into the EDIT
|
|
795
|
+
-- DM right before ExecutePlayModeAsync. ExecutePlayModeAsync clones the
|
|
796
|
+
-- DataModel into the play DMs, so the scripts come along and run there.
|
|
797
|
+
-- TestHandlers cleans them up from the edit DM when ExecutePlayModeAsync
|
|
798
|
+
-- returns (test ended for any reason: stop_playtest, manual close, EndTest).
|
|
799
|
+
-- Both scripts have Archivable=false so a user save doesn't persist them.
|
|
800
|
+
local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
801
|
+
local ServerScriptService = _services.ServerScriptService
|
|
802
|
+
local StarterPlayer = _services.StarterPlayer
|
|
803
|
+
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
804
|
+
local function getStarterPlayerScripts()
|
|
805
|
+
return StarterPlayer:FindFirstChild("StarterPlayerScripts")
|
|
806
|
+
end
|
|
807
|
+
local SERVER_SCRIPT_NAME = "__MCP_ServerEvalBridge"
|
|
808
|
+
local CLIENT_SCRIPT_NAME = "__MCP_ClientEvalBridge"
|
|
809
|
+
-- Public so the eval_*_runtime tool wrappers can reference the same names.
|
|
810
|
+
local BRIDGE_NAMES = {
|
|
811
|
+
serverScript = SERVER_SCRIPT_NAME,
|
|
812
|
+
clientScript = CLIENT_SCRIPT_NAME,
|
|
813
|
+
serverRemote = "__MCP_ServerEvalRemote",
|
|
814
|
+
serverLocal = "__MCP_ServerEvalLocal",
|
|
815
|
+
clientLocal = "__MCP_ClientEvalBridge",
|
|
816
|
+
}
|
|
817
|
+
-- Embedded Luau. The double `${...}` references our exported names so a
|
|
818
|
+
-- rename here propagates to both the script source and the tool wrappers.
|
|
819
|
+
local SERVER_BRIDGE_SOURCE = `\
|
|
820
|
+
-- Auto-installed by @chrrxs/robloxstudio-mcp at start_playtest, removed at\
|
|
821
|
+
-- stop_playtest. Provides shared-require-cache eval on the server peer for\
|
|
822
|
+
-- the eval_server_runtime MCP tool.\
|
|
823
|
+
\
|
|
824
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")\
|
|
825
|
+
local ServerScriptService = game:GetService("ServerScriptService")\
|
|
826
|
+
local RunService = game:GetService("RunService")\
|
|
827
|
+
\
|
|
828
|
+
if not RunService:IsStudio() then\
|
|
829
|
+
return\
|
|
830
|
+
end\
|
|
831
|
+
\
|
|
832
|
+
local function evalCode(source)\
|
|
833
|
+
if type(source) ~= "string" then\
|
|
834
|
+
return false, "source must be a string"\
|
|
835
|
+
end\
|
|
836
|
+
local fn, compileErr = loadstring(source, "MCPServerEval")\
|
|
837
|
+
if not fn then\
|
|
838
|
+
local errStr = tostring(compileErr or "loadstring returned nil")\
|
|
839
|
+
-- Roblox returns nil from loadstring when LoadStringEnabled=false.\
|
|
840
|
+
-- Surface a clear, actionable error.\
|
|
841
|
+
if string.find(errStr, "not enabled", 1, true)\
|
|
842
|
+
or string.find(errStr, "disabled", 1, true)\
|
|
843
|
+
or errStr == "loadstring returned nil"\
|
|
844
|
+
then\
|
|
845
|
+
return false,\
|
|
846
|
+
"ServerScriptService.LoadStringEnabled is false. eval_server_runtime requires it. "\
|
|
847
|
+
.. "Enable it in Studio (ServerScriptService > Properties > LoadStringEnabled = true) "\
|
|
848
|
+
.. "and restart the playtest."\
|
|
849
|
+
end\
|
|
850
|
+
return false, errStr\
|
|
851
|
+
end\
|
|
852
|
+
return pcall(fn)\
|
|
853
|
+
end\
|
|
854
|
+
\
|
|
855
|
+
-- Defensive cleanup of stale instances from a prior session.\
|
|
856
|
+
local prevRf = ReplicatedStorage:FindFirstChild("{BRIDGE_NAMES.serverRemote}")\
|
|
857
|
+
if prevRf then prevRf:Destroy() end\
|
|
858
|
+
local prevBf = ServerScriptService:FindFirstChild("{BRIDGE_NAMES.serverLocal}")\
|
|
859
|
+
if prevBf then prevBf:Destroy() end\
|
|
860
|
+
\
|
|
861
|
+
local rf = Instance.new("RemoteFunction")\
|
|
862
|
+
rf.Name = "{BRIDGE_NAMES.serverRemote}"\
|
|
863
|
+
rf.Archivable = false\
|
|
864
|
+
rf.Parent = ReplicatedStorage\
|
|
865
|
+
rf.OnServerInvoke = function(_player, source)\
|
|
866
|
+
return evalCode(source)\
|
|
867
|
+
end\
|
|
868
|
+
\
|
|
869
|
+
local bf = Instance.new("BindableFunction")\
|
|
870
|
+
bf.Name = "{BRIDGE_NAMES.serverLocal}"\
|
|
871
|
+
bf.Archivable = false\
|
|
872
|
+
bf.Parent = ServerScriptService\
|
|
873
|
+
bf.OnInvoke = function(source)\
|
|
874
|
+
return evalCode(source)\
|
|
875
|
+
end\
|
|
876
|
+
`
|
|
877
|
+
local CLIENT_BRIDGE_SOURCE = `\
|
|
878
|
+
-- Auto-installed by @chrrxs/robloxstudio-mcp at start_playtest, removed at\
|
|
879
|
+
-- stop_playtest. Provides shared-require-cache eval on the client peer for\
|
|
880
|
+
-- the eval_client_runtime MCP tool.\
|
|
881
|
+
\
|
|
882
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")\
|
|
883
|
+
local RunService = game:GetService("RunService")\
|
|
884
|
+
\
|
|
885
|
+
if not RunService:IsStudio() then\
|
|
886
|
+
return\
|
|
887
|
+
end\
|
|
888
|
+
\
|
|
889
|
+
local prevBf = ReplicatedStorage:FindFirstChild("{BRIDGE_NAMES.clientLocal}")\
|
|
890
|
+
if prevBf then prevBf:Destroy() end\
|
|
891
|
+
\
|
|
892
|
+
local bf = Instance.new("BindableFunction")\
|
|
893
|
+
bf.Name = "{BRIDGE_NAMES.clientLocal}"\
|
|
894
|
+
bf.Archivable = false\
|
|
895
|
+
bf.Parent = ReplicatedStorage\
|
|
896
|
+
bf.OnInvoke = function(payload)\
|
|
897
|
+
if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then\
|
|
898
|
+
return false, "payload must be a ModuleScript instance"\
|
|
899
|
+
end\
|
|
900
|
+
return pcall(require, payload)\
|
|
901
|
+
end\
|
|
902
|
+
`
|
|
903
|
+
local function setSource(scriptInst, source)
|
|
904
|
+
-- ScriptEditorService is the cleaner API and integrates with Studio's
|
|
905
|
+
-- edit history; fall back to direct Source mutation (allowed in plugin
|
|
906
|
+
-- context with PluginSecurity) if the edit service rejects the call.
|
|
907
|
+
local seOk = pcall(function()
|
|
908
|
+
ScriptEditorService:UpdateSourceAsync(scriptInst, function()
|
|
909
|
+
return source
|
|
910
|
+
end)
|
|
911
|
+
end)
|
|
912
|
+
if not seOk then
|
|
913
|
+
scriptInst.Source = source
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
local function findBridges()
|
|
917
|
+
local sps = getStarterPlayerScripts()
|
|
918
|
+
return {
|
|
919
|
+
server = ServerScriptService:FindFirstChild(SERVER_SCRIPT_NAME),
|
|
920
|
+
client = if sps then sps:FindFirstChild(CLIENT_SCRIPT_NAME) else nil,
|
|
921
|
+
}
|
|
922
|
+
end
|
|
923
|
+
local function cleanupBridges()
|
|
924
|
+
local _binding = findBridges()
|
|
925
|
+
local server = _binding.server
|
|
926
|
+
local client = _binding.client
|
|
927
|
+
if server then
|
|
928
|
+
pcall(function()
|
|
929
|
+
return server:Destroy()
|
|
930
|
+
end)
|
|
931
|
+
end
|
|
932
|
+
if client then
|
|
933
|
+
pcall(function()
|
|
934
|
+
return client:Destroy()
|
|
935
|
+
end)
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
local function installBridges()
|
|
939
|
+
-- Defensive: clear any stale bridges from a prior unclean exit before
|
|
940
|
+
-- inserting fresh. The injected script also self-cleans its
|
|
941
|
+
-- ReplicatedStorage/ServerScriptService children at startup, but the
|
|
942
|
+
-- containing Script/LocalScript objects themselves we must clear here.
|
|
943
|
+
cleanupBridges()
|
|
944
|
+
local ok, err = pcall(function()
|
|
945
|
+
local serverScript = Instance.new("Script")
|
|
946
|
+
serverScript.Name = SERVER_SCRIPT_NAME
|
|
947
|
+
serverScript.Archivable = false
|
|
948
|
+
setSource(serverScript, SERVER_BRIDGE_SOURCE)
|
|
949
|
+
serverScript.Parent = ServerScriptService
|
|
950
|
+
local sps = getStarterPlayerScripts()
|
|
951
|
+
if not sps then
|
|
952
|
+
error("StarterPlayer.StarterPlayerScripts not found - cannot install client eval bridge")
|
|
953
|
+
end
|
|
954
|
+
local clientScript = Instance.new("LocalScript")
|
|
955
|
+
clientScript.Name = CLIENT_SCRIPT_NAME
|
|
956
|
+
clientScript.Archivable = false
|
|
957
|
+
setSource(clientScript, CLIENT_BRIDGE_SOURCE)
|
|
958
|
+
clientScript.Parent = sps
|
|
959
|
+
end)
|
|
960
|
+
if not ok then
|
|
961
|
+
return {
|
|
962
|
+
installed = false,
|
|
963
|
+
error = tostring(err),
|
|
964
|
+
}
|
|
965
|
+
end
|
|
966
|
+
return {
|
|
967
|
+
installed = true,
|
|
968
|
+
}
|
|
969
|
+
end
|
|
970
|
+
-- Heuristic check so start_playtest can surface a warning when
|
|
971
|
+
-- LoadStringEnabled is false (eval_server_runtime won't work in that mode).
|
|
972
|
+
-- We can't import the runtime LoadStringEnabled value cleanly without
|
|
973
|
+
-- pulling in the type — read defensively.
|
|
974
|
+
local function loadStringEnabled()
|
|
975
|
+
local ok, value = pcall(function()
|
|
976
|
+
return ServerScriptService.LoadStringEnabled
|
|
977
|
+
end)
|
|
978
|
+
return ok and value == true
|
|
979
|
+
end
|
|
980
|
+
return {
|
|
981
|
+
cleanupBridges = cleanupBridges,
|
|
982
|
+
installBridges = installBridges,
|
|
983
|
+
loadStringEnabled = loadStringEnabled,
|
|
984
|
+
BRIDGE_NAMES = BRIDGE_NAMES,
|
|
985
|
+
}
|
|
986
|
+
]]></string>
|
|
987
|
+
</Properties>
|
|
988
|
+
</Item>
|
|
989
|
+
<Item class="Folder" referent="5">
|
|
778
990
|
<Properties>
|
|
779
991
|
<string name="Name">handlers</string>
|
|
780
992
|
</Properties>
|
|
781
|
-
<Item class="ModuleScript" referent="
|
|
993
|
+
<Item class="ModuleScript" referent="6">
|
|
782
994
|
<Properties>
|
|
783
995
|
<string name="Name">AssetHandlers</string>
|
|
784
996
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -1064,7 +1276,7 @@ return {
|
|
|
1064
1276
|
]]></string>
|
|
1065
1277
|
</Properties>
|
|
1066
1278
|
</Item>
|
|
1067
|
-
<Item class="ModuleScript" referent="
|
|
1279
|
+
<Item class="ModuleScript" referent="7">
|
|
1068
1280
|
<Properties>
|
|
1069
1281
|
<string name="Name">BuildHandlers</string>
|
|
1070
1282
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -1629,7 +1841,7 @@ return {
|
|
|
1629
1841
|
]]></string>
|
|
1630
1842
|
</Properties>
|
|
1631
1843
|
</Item>
|
|
1632
|
-
<Item class="ModuleScript" referent="
|
|
1844
|
+
<Item class="ModuleScript" referent="8">
|
|
1633
1845
|
<Properties>
|
|
1634
1846
|
<string name="Name">CaptureHandlers</string>
|
|
1635
1847
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -1795,7 +2007,7 @@ return {
|
|
|
1795
2007
|
]]></string>
|
|
1796
2008
|
</Properties>
|
|
1797
2009
|
</Item>
|
|
1798
|
-
<Item class="ModuleScript" referent="
|
|
2010
|
+
<Item class="ModuleScript" referent="9">
|
|
1799
2011
|
<Properties>
|
|
1800
2012
|
<string name="Name">InputHandlers</string>
|
|
1801
2013
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -1949,7 +2161,7 @@ return {
|
|
|
1949
2161
|
]]></string>
|
|
1950
2162
|
</Properties>
|
|
1951
2163
|
</Item>
|
|
1952
|
-
<Item class="ModuleScript" referent="
|
|
2164
|
+
<Item class="ModuleScript" referent="10">
|
|
1953
2165
|
<Properties>
|
|
1954
2166
|
<string name="Name">InstanceHandlers</string>
|
|
1955
2167
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -2456,7 +2668,7 @@ return {
|
|
|
2456
2668
|
]]></string>
|
|
2457
2669
|
</Properties>
|
|
2458
2670
|
</Item>
|
|
2459
|
-
<Item class="ModuleScript" referent="
|
|
2671
|
+
<Item class="ModuleScript" referent="11">
|
|
2460
2672
|
<Properties>
|
|
2461
2673
|
<string name="Name">MetadataHandlers</string>
|
|
2462
2674
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3039,7 +3251,7 @@ return {
|
|
|
3039
3251
|
]]></string>
|
|
3040
3252
|
</Properties>
|
|
3041
3253
|
</Item>
|
|
3042
|
-
<Item class="ModuleScript" referent="
|
|
3254
|
+
<Item class="ModuleScript" referent="12">
|
|
3043
3255
|
<Properties>
|
|
3044
3256
|
<string name="Name">PropertyHandlers</string>
|
|
3045
3257
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3291,7 +3503,7 @@ return {
|
|
|
3291
3503
|
]]></string>
|
|
3292
3504
|
</Properties>
|
|
3293
3505
|
</Item>
|
|
3294
|
-
<Item class="ModuleScript" referent="
|
|
3506
|
+
<Item class="ModuleScript" referent="13">
|
|
3295
3507
|
<Properties>
|
|
3296
3508
|
<string name="Name">QueryHandlers</string>
|
|
3297
3509
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -4318,7 +4530,7 @@ return {
|
|
|
4318
4530
|
]]></string>
|
|
4319
4531
|
</Properties>
|
|
4320
4532
|
</Item>
|
|
4321
|
-
<Item class="ModuleScript" referent="
|
|
4533
|
+
<Item class="ModuleScript" referent="14">
|
|
4322
4534
|
<Properties>
|
|
4323
4535
|
<string name="Name">ScriptHandlers</string>
|
|
4324
4536
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5014,7 +5226,7 @@ return {
|
|
|
5014
5226
|
]]></string>
|
|
5015
5227
|
</Properties>
|
|
5016
5228
|
</Item>
|
|
5017
|
-
<Item class="ModuleScript" referent="
|
|
5229
|
+
<Item class="ModuleScript" referent="15">
|
|
5018
5230
|
<Properties>
|
|
5019
5231
|
<string name="Name">TestHandlers</string>
|
|
5020
5232
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5022,6 +5234,10 @@ local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
|
5022
5234
|
local _services = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
5023
5235
|
local HttpService = _services.HttpService
|
|
5024
5236
|
local LogService = _services.LogService
|
|
5237
|
+
local _EvalBridges = TS.import(script, script.Parent.Parent, "EvalBridges")
|
|
5238
|
+
local installBridges = _EvalBridges.installBridges
|
|
5239
|
+
local cleanupBridges = _EvalBridges.cleanupBridges
|
|
5240
|
+
local loadStringEnabled = _EvalBridges.loadStringEnabled
|
|
5025
5241
|
local StudioTestService = game:GetService("StudioTestService")
|
|
5026
5242
|
local ServerScriptService = game:GetService("ServerScriptService")
|
|
5027
5243
|
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
@@ -5178,6 +5394,14 @@ local function startPlaytest(requestData)
|
|
|
5178
5394
|
if not injected then
|
|
5179
5395
|
warn(`[MCP] Failed to inject stop listener: {injErr}`)
|
|
5180
5396
|
end
|
|
5397
|
+
-- Auto-install the game-VM eval bridges (ServerEvalBridge + ClientEvalBridge)
|
|
5398
|
+
-- so eval_server_runtime / eval_client_runtime work without manual setup.
|
|
5399
|
+
-- Bridges are cleaned up from the edit DM after the play DMs tear down.
|
|
5400
|
+
local bridgeInstall = installBridges()
|
|
5401
|
+
local hasLoadString = loadStringEnabled()
|
|
5402
|
+
if not bridgeInstall.installed then
|
|
5403
|
+
warn(`[MCP] Eval bridge install failed: {bridgeInstall.error}`)
|
|
5404
|
+
end
|
|
5181
5405
|
if numPlayers ~= nil and mode == "run" then
|
|
5182
5406
|
local TestService = game:GetService("TestService")
|
|
5183
5407
|
TestService.NumberOfPlayers = math.clamp(numPlayers, 1, 8)
|
|
@@ -5200,39 +5424,34 @@ local function startPlaytest(requestData)
|
|
|
5200
5424
|
end
|
|
5201
5425
|
testRunning = false
|
|
5202
5426
|
cleanupStopListener()
|
|
5427
|
+
cleanupBridges()
|
|
5203
5428
|
end)
|
|
5204
5429
|
local msg = if numPlayers ~= nil then `Playtest started in {mode} mode with {numPlayers} player(s)` else `Playtest started in {mode} mode`
|
|
5205
|
-
|
|
5430
|
+
local response = {
|
|
5206
5431
|
success = true,
|
|
5207
5432
|
message = msg,
|
|
5433
|
+
evalBridges = if bridgeInstall.installed then "installed" else `failed: {bridgeInstall.error}`,
|
|
5208
5434
|
}
|
|
5435
|
+
-- Surface loadstring availability up-front so callers know whether
|
|
5436
|
+
-- eval_server_runtime will work before they try it. eval_client_runtime
|
|
5437
|
+
-- doesn't need loadstring (it uses ModuleScript+require), so this only
|
|
5438
|
+
-- affects the server bridge.
|
|
5439
|
+
if not hasLoadString then
|
|
5440
|
+
response.serverEvalNote = "ServerScriptService.LoadStringEnabled is false. eval_server_runtime will not work " .. "until you enable it (ServerScriptService > Properties > LoadStringEnabled = true) " .. "and restart the playtest. eval_client_runtime is unaffected."
|
|
5441
|
+
end
|
|
5442
|
+
return response
|
|
5209
5443
|
end
|
|
5210
5444
|
local function stopPlaytest(_requestData)
|
|
5211
|
-
--
|
|
5212
|
-
--
|
|
5213
|
-
-- only
|
|
5214
|
-
--
|
|
5215
|
-
--
|
|
5216
|
-
--
|
|
5217
|
-
|
|
5218
|
-
local endOk, endErr = pcall(function()
|
|
5219
|
-
endTest:EndTest("stopped_by_mcp")
|
|
5220
|
-
end)
|
|
5221
|
-
if endOk then
|
|
5222
|
-
local _object = {
|
|
5223
|
-
success = true,
|
|
5224
|
-
}
|
|
5225
|
-
local _left = "output"
|
|
5226
|
-
local _array = {}
|
|
5227
|
-
local _length = #_array
|
|
5228
|
-
table.move(outputBuffer, 1, #outputBuffer, _length + 1, _array)
|
|
5229
|
-
_object[_left] = _array
|
|
5230
|
-
_object.outputCount = #outputBuffer
|
|
5231
|
-
_object.message = "Playtest stopped via StudioTestService."
|
|
5232
|
-
return _object
|
|
5233
|
-
end
|
|
5445
|
+
-- Server-side routing (tools/index.ts:stopPlaytest) sends /api/stop-playtest
|
|
5446
|
+
-- to the role="edit-proxy" instance whenever one is registered. This handler
|
|
5447
|
+
-- is only reached when there's no edit-proxy - i.e. no active playtest, or
|
|
5448
|
+
-- the play DMs haven't completed plugin auto-activation yet. Calling
|
|
5449
|
+
-- StudioTestService:EndTest from the edit DM is illegal ("can only be
|
|
5450
|
+
-- called from the server DataModel of a running Studio play session"), so
|
|
5451
|
+
-- don't try - return a clean "no active playtest" response instead.
|
|
5234
5452
|
return {
|
|
5235
|
-
error =
|
|
5453
|
+
error = "No active playtest to stop (edit-proxy not registered).",
|
|
5454
|
+
hint = "If a playtest is running, the play-server DM may not have completed plugin auto-activation yet. " .. "Wait a moment and retry, or call execute_luau target=server with StudioTestService:EndTest as a manual fallback.",
|
|
5236
5455
|
}
|
|
5237
5456
|
end
|
|
5238
5457
|
local function getPlaytestOutput(_requestData)
|
|
@@ -5326,7 +5545,7 @@ return {
|
|
|
5326
5545
|
</Properties>
|
|
5327
5546
|
</Item>
|
|
5328
5547
|
</Item>
|
|
5329
|
-
<Item class="ModuleScript" referent="
|
|
5548
|
+
<Item class="ModuleScript" referent="16">
|
|
5330
5549
|
<Properties>
|
|
5331
5550
|
<string name="Name">Recording</string>
|
|
5332
5551
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5356,11 +5575,11 @@ return {
|
|
|
5356
5575
|
]]></string>
|
|
5357
5576
|
</Properties>
|
|
5358
5577
|
</Item>
|
|
5359
|
-
<Item class="ModuleScript" referent="
|
|
5578
|
+
<Item class="ModuleScript" referent="17">
|
|
5360
5579
|
<Properties>
|
|
5361
5580
|
<string name="Name">State</string>
|
|
5362
5581
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
5363
|
-
local CURRENT_VERSION = "2.
|
|
5582
|
+
local CURRENT_VERSION = "2.9.0"
|
|
5364
5583
|
local MAX_CONNECTIONS = 5
|
|
5365
5584
|
local BASE_PORT = 58741
|
|
5366
5585
|
local activeTabIndex = 0
|
|
@@ -5452,7 +5671,7 @@ return {
|
|
|
5452
5671
|
]]></string>
|
|
5453
5672
|
</Properties>
|
|
5454
5673
|
</Item>
|
|
5455
|
-
<Item class="ModuleScript" referent="
|
|
5674
|
+
<Item class="ModuleScript" referent="18">
|
|
5456
5675
|
<Properties>
|
|
5457
5676
|
<string name="Name">UI</string>
|
|
5458
5677
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5674,7 +5893,7 @@ local function updateTabLabel(connIndex)
|
|
|
5674
5893
|
end
|
|
5675
5894
|
local function init(pluginRef)
|
|
5676
5895
|
local CURRENT_VERSION = State.CURRENT_VERSION
|
|
5677
|
-
local screenGui = pluginRef:CreateDockWidgetPluginGuiAsync("MCPServerInterface", DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false,
|
|
5896
|
+
local screenGui = pluginRef:CreateDockWidgetPluginGuiAsync("MCPServerInterface", DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Float, false, true, 300, 260, 260, 200))
|
|
5678
5897
|
screenGui.Title = `MCP Server v{CURRENT_VERSION}`
|
|
5679
5898
|
local mainFrame = Instance.new("Frame")
|
|
5680
5899
|
mainFrame.Size = UDim2.new(1, 0, 1, 0)
|
|
@@ -6172,7 +6391,7 @@ return {
|
|
|
6172
6391
|
]]></string>
|
|
6173
6392
|
</Properties>
|
|
6174
6393
|
</Item>
|
|
6175
|
-
<Item class="ModuleScript" referent="
|
|
6394
|
+
<Item class="ModuleScript" referent="19">
|
|
6176
6395
|
<Properties>
|
|
6177
6396
|
<string name="Name">Utils</string>
|
|
6178
6397
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6702,11 +6921,11 @@ return {
|
|
|
6702
6921
|
</Properties>
|
|
6703
6922
|
</Item>
|
|
6704
6923
|
</Item>
|
|
6705
|
-
<Item class="Folder" referent="
|
|
6924
|
+
<Item class="Folder" referent="23">
|
|
6706
6925
|
<Properties>
|
|
6707
6926
|
<string name="Name">include</string>
|
|
6708
6927
|
</Properties>
|
|
6709
|
-
<Item class="ModuleScript" referent="
|
|
6928
|
+
<Item class="ModuleScript" referent="20">
|
|
6710
6929
|
<Properties>
|
|
6711
6930
|
<string name="Name">Promise</string>
|
|
6712
6931
|
<string name="Source"><![CDATA[--[[
|
|
@@ -8780,7 +8999,7 @@ return Promise
|
|
|
8780
8999
|
]]></string>
|
|
8781
9000
|
</Properties>
|
|
8782
9001
|
</Item>
|
|
8783
|
-
<Item class="ModuleScript" referent="
|
|
9002
|
+
<Item class="ModuleScript" referent="21">
|
|
8784
9003
|
<Properties>
|
|
8785
9004
|
<string name="Name">RuntimeLib</string>
|
|
8786
9005
|
<string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
|
|
@@ -9047,15 +9266,15 @@ return TS
|
|
|
9047
9266
|
</Properties>
|
|
9048
9267
|
</Item>
|
|
9049
9268
|
</Item>
|
|
9050
|
-
<Item class="Folder" referent="
|
|
9269
|
+
<Item class="Folder" referent="24">
|
|
9051
9270
|
<Properties>
|
|
9052
9271
|
<string name="Name">node_modules</string>
|
|
9053
9272
|
</Properties>
|
|
9054
|
-
<Item class="Folder" referent="
|
|
9273
|
+
<Item class="Folder" referent="25">
|
|
9055
9274
|
<Properties>
|
|
9056
9275
|
<string name="Name">@rbxts</string>
|
|
9057
9276
|
</Properties>
|
|
9058
|
-
<Item class="ModuleScript" referent="
|
|
9277
|
+
<Item class="ModuleScript" referent="22">
|
|
9059
9278
|
<Properties>
|
|
9060
9279
|
<string name="Name">services</string>
|
|
9061
9280
|
<string name="Source"><![CDATA[return setmetatable({}, {
|
|
@@ -94,7 +94,6 @@ function setupClientBroker() {
|
|
|
94
94
|
}
|
|
95
95
|
return identity<ExecuteResult>({ success: false, error: tostring(result) });
|
|
96
96
|
};
|
|
97
|
-
print("[MCPFork] client broker ready");
|
|
98
97
|
}
|
|
99
98
|
|
|
100
99
|
const proxyByPlayer = new Map<Player, ProxyEntry>();
|
|
@@ -141,19 +140,17 @@ function registerProxy(player: Player, rf: RemoteFunction) {
|
|
|
141
140
|
const body = HttpService.JSONDecode(res.Body) as ReadyResponseBody;
|
|
142
141
|
const assigned = body.assignedRole ?? "client";
|
|
143
142
|
proxyByPlayer.set(player, { instanceId: proxyId, role: assigned });
|
|
144
|
-
print(`[MCPFork] proxy ${assigned} -> ${player.Name}`);
|
|
145
143
|
task.spawn(pollProxy, proxyId, player, rf);
|
|
146
144
|
}
|
|
147
145
|
|
|
148
146
|
function startEditProxyLoop() {
|
|
149
147
|
task.spawn(() => {
|
|
150
148
|
const proxyId = HttpService.GenerateGUID(false);
|
|
151
|
-
const [ok, res] = postJson("/ready", { instanceId: proxyId, role: "edit" });
|
|
149
|
+
const [ok, res] = postJson("/ready", { instanceId: proxyId, role: "edit-proxy" });
|
|
152
150
|
if (!ok || !res || !res.Success) {
|
|
153
151
|
warn("[MCPFork] edit-proxy register failed");
|
|
154
152
|
return;
|
|
155
153
|
}
|
|
156
|
-
print("[MCPFork] edit-proxy ready (stop-playtest interceptor)");
|
|
157
154
|
while (true) {
|
|
158
155
|
const [okPoll, pollRes] = pcall(() =>
|
|
159
156
|
HttpService.RequestAsync({
|
|
@@ -210,7 +207,6 @@ function setupServerBroker() {
|
|
|
210
207
|
proxyByPlayer.clear();
|
|
211
208
|
});
|
|
212
209
|
startEditProxyLoop();
|
|
213
|
-
print("[MCPFork] server broker ready");
|
|
214
210
|
}
|
|
215
211
|
|
|
216
212
|
export = {
|