Implement get_script command for improved script retrieval in MCP

- Introduced a new `get_script` command to allow agents to read script source code directly from Roblox Studio, enhancing conflict resolution during sync operations.
- Updated the MCP stream handling to support the new command, including error handling for missing or invalid parameters.
- Enhanced the App component to manage the `get_script` command and its response, ensuring proper integration with the existing MCP functionality.
- Updated the TODO list to reflect the addition of the `get_script` command and its associated documentation requirements.

These changes aim to improve the overall functionality and user experience of the MCP integration, particularly in script management.
This commit is contained in:
Joe 2026-03-01 09:41:20 -06:00
parent 9aa3145b06
commit fd49602adb
8 changed files with 1487 additions and 207 deletions

View file

@ -211,6 +211,9 @@ function App:init()
onSyncCommand = function(requestId, mode, overrides)
return self:startMcpSync(requestId, mode, overrides)
end,
onGetScriptCommand = function(requestId, data)
return self:handleMcpGetScript(requestId, data)
end,
getPluginConfig = function(key)
return Settings:get(key)
end,
@ -239,6 +242,7 @@ function App:willUnmount()
if self.mcpStream then
self.mcpStream:stop()
self.mcpStream = nil
self._lastMcpFromIds = nil
end
end
@ -1186,6 +1190,12 @@ function App:startMcpSync(requestId, mode, overrides)
serveSession:setConfirmCallback(function(instanceMap, patch, serverInfo)
cachedServerInfo = serverInfo
local retainedFromIds = {}
for id, inst in instanceMap.fromIds do
retainedFromIds[id] = inst
end
self._lastMcpFromIds = retainedFromIds
PatchSet.removeDataModelName(patch, instanceMap)
if PatchSet.isEmpty(patch) then
@ -1538,6 +1548,85 @@ function App:_buildMcpChangeList(patchTree, patch, instanceMap, selections, incl
return changes
end
function App:handleMcpGetScript(requestId, params)
local ScriptEditorService = game:GetService("ScriptEditorService")
return Promise.resolve():andThen(function()
local fromIds = self._lastMcpFromIds
if not fromIds then
return {
requestId = requestId,
status = "error",
message = 'No previous sync session. Run atlas_sync(mode: "dryrun") first to establish the instance mapping.',
}
end
local id = params.id
if not id then
return {
requestId = requestId,
status = "error",
message = "No id provided. The server should have resolved fsPath to an id.",
}
end
local instance = fromIds[id]
if not instance then
return {
requestId = requestId,
status = "error",
message = "Instance not found by id. The sync session may be stale, run atlas_sync again.",
}
end
if instance.Parent == nil then
return {
requestId = requestId,
status = "error",
message = "Instance was deleted since last sync. Run atlas_sync again.",
}
end
if not instance:IsA("LuaSourceContainer") then
return {
requestId = requestId,
status = "error",
message = `Instance is not a script (class: {instance.ClassName})`,
}
end
local source
local isDraft = params.fromDraft == true
if isDraft then
local ok, draft = pcall(ScriptEditorService.GetEditorSource, ScriptEditorService, instance)
if ok then
source = draft
else
source = instance.Source
isDraft = false
end
else
source = instance.Source
end
local gitBlob = "blob " .. tostring(#source) .. "\0" .. source
local studioHash = SHA1(buffer.fromstring(gitBlob))
local fullName = instance:GetFullName()
local instancePath = string.gsub(fullName, "^game%.", "")
instancePath = string.gsub(instancePath, "%.", "/")
return {
requestId = requestId,
status = "success",
source = source,
studioHash = studioHash,
className = instance.ClassName,
instancePath = instancePath,
isDraft = isDraft,
}
end)
end
function App:render()
local pluginName = "Atlas " .. Version.display(Config.version)

View file

@ -17,6 +17,7 @@ function McpStream.new(options)
local self = setmetatable({}, McpStream)
self._onSyncCommand = options.onSyncCommand
self._onGetScriptCommand = options.onGetScriptCommand
self._getPluginConfig = options.getPluginConfig
self._wsClient = nil
self._running = false
@ -125,6 +126,33 @@ function McpStream:_tryConnect()
end)
end
end)
elseif data.type == "getScript" and data.requestId then
Log.info("MCP stream: received getScript command (requestId={})", data.requestId)
local resultPromise = self._onGetScriptCommand(data.requestId, data)
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: getScript command failed: {}", tostring(err))
if self._wsClient then
local errorResult = HttpService:JSONEncode({
requestId = data.requestId,
status = "error",
message = tostring(err),
})
pcall(function()
self._wsClient:Send(errorResult)
end)
end
end)
end
end)