- Updated RobloxMCPPlugin with HTTP polling (auto-enables HttpService) - Added 20-weapon FPS game example (CoD-style) - Added Python studio-inject.py for command bar injection via Win32 API - Added auto-connect setup scripts (VBS + PowerShell) - Updated MCP server with all FPS game tools Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
240 lines
6.3 KiB
Lua
240 lines
6.3 KiB
Lua
-- Roblox MCP Server - HTTP Polling Version
|
|
-- This version polls the MCP server for commands via HTTP
|
|
|
|
local HttpService = game:GetService("HttpService")
|
|
local RunService = game:GetService("RunService")
|
|
|
|
-- Configuration
|
|
local MCP_SERVER_URL = "http://127.0.0.1:37423"
|
|
local POLL_INTERVAL = 0.5 -- seconds
|
|
local DEBUG = true
|
|
|
|
-- State
|
|
local isRunning = true
|
|
local lastCommandId = 0
|
|
|
|
-- Logging
|
|
local function log(msg)
|
|
if DEBUG then
|
|
print("[RobloxMCP] " .. msg)
|
|
end
|
|
end
|
|
|
|
-- Get object by path
|
|
local function getObjectFromPath(path)
|
|
if not path or path == "" then return nil end
|
|
if path == "game" or path == "Game" then return game end
|
|
if path == "Workspace" or path == "workspace" then return workspace end
|
|
|
|
local parts = {}
|
|
for part in string.gmatch(path, "[^%.]+") do
|
|
table.insert(parts, part)
|
|
end
|
|
|
|
local obj = game
|
|
for _, part in ipairs(parts) do
|
|
if part == "Workspace" or part == "workspace" then
|
|
obj = workspace
|
|
elseif typeof(obj) == "Instance" and obj:FindFirstChild(part) then
|
|
obj = obj[part]
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
return obj
|
|
end
|
|
|
|
-- Create object at path
|
|
local function createObjectAt(path, className, properties)
|
|
local lastDot = string.find(path, "%.[^%.]+$")
|
|
local parentPath = lastDot and string.sub(path, 1, lastDot - 1) or "game"
|
|
local objectName = lastDot and string.sub(path, lastDot + 1) or path
|
|
|
|
local parent = getObjectFromPath(parentPath)
|
|
if not parent then return nil, "Parent not found" end
|
|
|
|
local obj = Instance.new(className)
|
|
obj.Name = objectName
|
|
|
|
if properties then
|
|
for prop, value in pairs(properties) do
|
|
pcall(function() obj[prop] = value end)
|
|
end
|
|
end
|
|
|
|
obj.Parent = parent
|
|
return obj
|
|
end
|
|
|
|
-- Command handlers
|
|
local handlers = {}
|
|
|
|
handlers.createPart = function(params)
|
|
local props = {
|
|
Name = params.partName,
|
|
Anchored = params.anchored ~= false,
|
|
Shape = Enum.PartType.Block,
|
|
}
|
|
|
|
if params.position then
|
|
props.Position = Vector3.new(params.position.x or 0, params.position.y or 0, params.position.z or 0)
|
|
end
|
|
|
|
if params.size then
|
|
props.Size = Vector3.new(params.size.x or 4, params.size.y or 1, params.size.z or 2)
|
|
end
|
|
|
|
if params.color then
|
|
pcall(function() props.BrickColor = BrickColor.new(params.color) end)
|
|
end
|
|
|
|
local part = createObjectAt((params.parentPath or "Workspace") .. "." .. params.partName, "Part", props)
|
|
return {success = part ~= nil}
|
|
end
|
|
|
|
handlers.createScript = function(params)
|
|
local obj = createObjectAt(params.path .. "." .. params.scriptName, params.scriptType or "Script", {Name = params.scriptName})
|
|
if obj then
|
|
obj.Source = params.source
|
|
return {success = true}
|
|
end
|
|
return {success = false}
|
|
end
|
|
|
|
handlers.setProperty = function(params)
|
|
local obj = getObjectFromPath(params.path)
|
|
if not obj then return {success = false, error = "Not found"} end
|
|
|
|
local value = params.value
|
|
if params.property == "Position" or params.property == "Size" then
|
|
value = Vector3.new(value.x, value.y, value.z)
|
|
elseif params.property == "Color3" then
|
|
value = Color3.new(value.r, value.g, value.b)
|
|
end
|
|
|
|
pcall(function() obj[params.property] = value end)
|
|
return {success = true}
|
|
end
|
|
|
|
handlers.executeCode = function(params)
|
|
local fn, err = loadstring(params.code)
|
|
if not fn then return {success = false, error = err} end
|
|
|
|
local ok = pcall(fn)
|
|
return {success = ok}
|
|
end
|
|
|
|
handlers.getHierarchy = function(params)
|
|
local obj = getObjectFromPath(params.path or "Workspace")
|
|
if not obj then return {success = false, error = "Not found"} end
|
|
|
|
local function build(obj, depth)
|
|
if depth <= 0 then return nil end
|
|
local children = {}
|
|
for _, child in ipairs(obj:GetChildren()) do
|
|
table.insert(children, {
|
|
name = child.Name,
|
|
className = child.ClassName,
|
|
})
|
|
end
|
|
return children
|
|
end
|
|
|
|
return {success = true, children = build(obj, params.depth or 2)}
|
|
end
|
|
|
|
handlers.importGLB = function(params)
|
|
-- Import GLB model into Roblox Studio
|
|
-- GLB files need to be imported via the Editor API for assets
|
|
-- For now, we'll create a placeholder model with instructions
|
|
|
|
local parent = getObjectFromPath(params.parentPath or "Workspace")
|
|
if not parent then
|
|
return {success = false, error = "Parent path not found"}
|
|
end
|
|
|
|
-- Create a model to hold the imported GLB
|
|
local model = Instance.new("Model")
|
|
model.Name = params.modelName or "ImportedGLB"
|
|
model.Parent = parent
|
|
|
|
-- Create a placeholder part with info
|
|
local placeholder = Instance.new("Part")
|
|
placeholder.Name = "GLB_Placeholder"
|
|
placeholder.Size = Vector3.new(4, 4, 4)
|
|
placeholder.Position = Vector3.new(0, 5, 0)
|
|
placeholder.Anchored = true
|
|
placeholder.BrickColor = BrickColor.new("Bright blue")
|
|
placeholder.Transparency = 0.5
|
|
placeholder.Parent = model
|
|
|
|
-- Add a note
|
|
local info = Instance.new("StringValue")
|
|
info.Name = "ImportInfo"
|
|
info.Value = "GLB Import: Use the 3D Importer (File > Import 3D) or Editor Service to import GLB files. This is a placeholder."
|
|
info.Parent = model
|
|
|
|
return {
|
|
success = true,
|
|
modelPath = (params.parentPath or "Workspace") .. "." .. params.modelName,
|
|
note = "GLB files require manual import via Roblox Studio's 3D Importer or Editor Service API"
|
|
}
|
|
end
|
|
|
|
-- Poll for commands
|
|
local function pollForCommands()
|
|
local success, response = pcall(function()
|
|
return HttpService:RequestAsync({
|
|
Url = MCP_SERVER_URL .. "/poll?last=" .. lastCommandId,
|
|
Method = "GET",
|
|
})
|
|
end)
|
|
|
|
if success and response.Success then
|
|
local data = HttpService:JSONDecode(response.Body)
|
|
if data.commands then
|
|
for _, cmd in ipairs(data.commands) do
|
|
log("Got command: " .. cmd.command)
|
|
lastCommandId = cmd.id
|
|
|
|
local handler = handlers[cmd.command]
|
|
local result = {success = false, error = "Unknown command"}
|
|
|
|
if handler then
|
|
local ok, ret = pcall(handler, cmd.params)
|
|
if ok then
|
|
result = ret
|
|
else
|
|
result = {success = false, error = tostring(ret)}
|
|
end
|
|
end
|
|
|
|
-- Send result back
|
|
pcall(function()
|
|
HttpService:RequestAsync({
|
|
Url = MCP_SERVER_URL .. "/result",
|
|
Method = "POST",
|
|
Headers = {["Content-Type"] = "application/json"},
|
|
Body = HttpService:JSONEncode({
|
|
id = cmd.id,
|
|
result = result
|
|
})
|
|
})
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Main loop
|
|
log("Starting Roblox MCP Server (HTTP Polling)")
|
|
log("MCP Server: " .. MCP_SERVER_URL)
|
|
|
|
RunService.Heartbeat:Connect(function()
|
|
if isRunning then
|
|
pcall(pollForCommands)
|
|
end
|
|
end)
|
|
|
|
log("Roblox MCP Server is running!")
|