mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
- Added a new section in the README to document the integration of MCP (Model Context Protocol) for AI agents, detailing how to set up and use the `atlas_sync` tool. - Introduced `McpStream` to manage WebSocket connections for real-time sync commands between the plugin and the MCP server. - Enhanced the `App` component to initialize and manage the MCP stream, including handling sync commands and user confirmations. - Updated the API to support WebSocket upgrades for the MCP stream, allowing for efficient communication with connected plugins. - Improved the handling of sync commands, ensuring proper responses based on the current connection state and user selections. These changes aim to facilitate programmatic syncs to Roblox Studio, improving the workflow for teams using AI agents.
150 lines
3.3 KiB
Lua
150 lines
3.3 KiB
Lua
local HttpService = game:GetService("HttpService")
|
|
|
|
local Rojo = script:FindFirstAncestor("Rojo")
|
|
local Packages = Rojo.Packages
|
|
|
|
local Log = require(Packages.Log)
|
|
|
|
local Config = require(script.Parent.Config)
|
|
local strict = require(script.Parent.strict)
|
|
|
|
local RECONNECT_INTERVAL = 5
|
|
|
|
local McpStream = {}
|
|
McpStream.__index = McpStream
|
|
|
|
function McpStream.new(options)
|
|
local self = setmetatable({}, McpStream)
|
|
|
|
self._onSyncCommand = options.onSyncCommand
|
|
self._getPluginConfig = options.getPluginConfig
|
|
self._wsClient = nil
|
|
self._running = false
|
|
self._loopThread = nil
|
|
|
|
return self
|
|
end
|
|
|
|
function McpStream:start()
|
|
if self._running then
|
|
return
|
|
end
|
|
self._running = true
|
|
|
|
self._loopThread = task.spawn(function()
|
|
while self._running do
|
|
self:_tryConnect()
|
|
if self._running then
|
|
task.wait(RECONNECT_INTERVAL)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function McpStream:stop()
|
|
self._running = false
|
|
if self._loopThread then
|
|
task.cancel(self._loopThread)
|
|
self._loopThread = nil
|
|
end
|
|
self:_disconnect()
|
|
end
|
|
|
|
function McpStream:_disconnect()
|
|
if self._wsClient then
|
|
pcall(function()
|
|
self._wsClient:Destroy()
|
|
end)
|
|
self._wsClient = nil
|
|
end
|
|
end
|
|
|
|
function McpStream:_tryConnect()
|
|
local host = Config.defaultHost
|
|
local port = Config.defaultPort
|
|
local url = ("ws://%s:%s/api/mcp/stream"):format(host, port)
|
|
|
|
local success, wsClient =
|
|
pcall(HttpService.CreateWebStreamClient, HttpService, Enum.WebStreamClientType.WebSocket, { Url = url })
|
|
if not success then
|
|
return
|
|
end
|
|
|
|
self._wsClient = wsClient
|
|
Log.trace("MCP stream connected to {}", url)
|
|
|
|
local greeting = HttpService:JSONEncode({
|
|
type = "hello",
|
|
twoWaySync = self._getPluginConfig("twoWaySync"),
|
|
oneShotSync = self._getPluginConfig("oneShotSync"),
|
|
confirmationBehavior = self._getPluginConfig("confirmationBehavior"),
|
|
})
|
|
pcall(function()
|
|
wsClient:Send(greeting)
|
|
end)
|
|
|
|
local done = false
|
|
local closed, errored, received
|
|
|
|
received = wsClient.MessageReceived:Connect(function(msg)
|
|
local ok, data = pcall(HttpService.JSONDecode, HttpService, msg)
|
|
if not ok then
|
|
return
|
|
end
|
|
|
|
if data.type == "sync" and data.requestId then
|
|
Log.info("MCP stream: received sync command (requestId={})", data.requestId)
|
|
|
|
local resultPromise = self._onSyncCommand(data.requestId)
|
|
|
|
resultPromise
|
|
:andThen(function(result)
|
|
if self._wsClient then
|
|
local json = HttpService:JSONEncode(result)
|
|
pcall(function()
|
|
self._wsClient:Send(json)
|
|
end)
|
|
end
|
|
end)
|
|
:catch(function(err)
|
|
Log.warn("MCP stream: sync command failed: {}", tostring(err))
|
|
if self._wsClient then
|
|
local errorResult = HttpService:JSONEncode({
|
|
requestId = data.requestId,
|
|
status = "error",
|
|
changes = {},
|
|
message = tostring(err),
|
|
})
|
|
pcall(function()
|
|
self._wsClient:Send(errorResult)
|
|
end)
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
|
|
closed = wsClient.Closed:Connect(function()
|
|
done = true
|
|
closed:Disconnect()
|
|
errored:Disconnect()
|
|
received:Disconnect()
|
|
Log.trace("MCP stream disconnected")
|
|
end)
|
|
|
|
errored = wsClient.Error:Connect(function(_code, _msg)
|
|
done = true
|
|
closed:Disconnect()
|
|
errored:Disconnect()
|
|
received:Disconnect()
|
|
end)
|
|
|
|
while not done and self._running do
|
|
task.wait(1)
|
|
end
|
|
|
|
self:_disconnect()
|
|
end
|
|
|
|
return strict("McpStream", {
|
|
new = McpStream.new,
|
|
})
|