MCP in Practice

Building Roblox Games with AI Using MCP

Learn how to connect Claude Code to Roblox Studio via MCP and go from spawning your first part to automating full game systems — with practical examples at every level.

8 min read

The Model Context Protocol (MCP) lets AI assistants like Claude Code talk directly to Roblox Studio — reading your scene, writing scripts, inserting objects, and running code in real time. Instead of copy-pasting snippets back and forth, you get a live two-way bridge between the AI and your game.

This article walks you from zero to advanced: setting up the connection, then building increasingly complex things with Claude Code as your co-developer.

How the MCP Bridge Works

Claude Code  ──stdio──►  MCP Server (local binary)  ──HTTP poll──►  Roblox Studio Plugin

                                                                      └── reads/writes your Place

The MCP server runs on your machine. A Studio plugin long-polls it for commands. When Claude calls a tool (e.g. run_code), the plugin executes it inside Studio and returns the result. Nothing leaves your machine except the prompts you send to Claude.


Setup

1. Install the MCP Server

Roblox now ships an MCP server built into Studio (as of February 2026). It is the recommended option — no Rust toolchain required.

Open Roblox Studio → File → Studio Settings → Beta Features → enable MCP Server.

Studio will start listening on localhost:3004 by default.

For the standalone open-source server (legacy), see the studio-rust-mcp-server repo. It is no longer actively updated but works as a reference.

2. Register it with Claude Code

claude mcp add roblox-studio --transport http http://localhost:3004/mcp

Or, if you are using the standalone binary on macOS:

claude mcp add --transport stdio roblox_studio \
  -- '/Applications/RobloxStudioMCP.app/Contents/MacOS/rbx-studio-mcp' --stdio

Verify the tools are available:

claude mcp list
# roblox-studio  ✓  tools: run_code, insert_model, get_console_output, ...

3. Open a Place in Studio

MCP commands operate on the currently open place. Open any baseplate and keep Studio visible — you will see changes happen live as Claude works.


Beginner: Spawning Objects and Running Scripts

Spawn a colored part

Tell Claude:

“In Roblox Studio, create a bright red brick part at position (0, 5, 0) with size (4, 4, 4).”

Claude calls run_code with:

local part = Instance.new("Part")
part.Size = Vector3.new(4, 4, 4)
part.Position = Vector3.new(0, 5, 0)
part.BrickColor = BrickColor.new("Bright red")
part.Anchored = true
part.Parent = workspace

The part appears in Studio instantly.

“List every object currently in Workspace.”

for _, obj in ipairs(workspace:GetChildren()) do
    print(obj.Name, obj.ClassName)
end

Claude calls get_console_output afterwards to read the result and shows you the list directly in the chat.

Insert a free model

“Insert a SpawnLocation from the Creator Store.”

Claude calls insert_model with the asset ID. The model drops into your scene — no manual searching in the toolbox required.


Intermediate: Building Game Systems

Basic coin pickup system

“Create a coin pickup system. Coins should be yellow spheres scattered at random positions. When a player touches one it disappears and prints a message.”

Claude writes and inserts two scripts:

ServerScript (inside ServerScriptService):

local function createCoin(position)
    local coin = Instance.new("Part")
    coin.Name = "Coin"
    coin.Shape = Enum.PartType.Ball
    coin.Size = Vector3.new(1, 1, 1)
    coin.BrickColor = BrickColor.new("Bright yellow")
    coin.Material = Enum.Material.Neon
    coin.Position = position
    coin.Anchored = true
    coin.Parent = workspace

    coin.Touched:Connect(function(hit)
        local character = hit.Parent
        local player = game.Players:GetPlayerFromCharacter(character)
        if player then
            print(player.Name .. " collected a coin!")
            coin:Destroy()
        end
    end)
end

-- Spawn 10 coins at random positions
for i = 1, 10 do
    local x = math.random(-20, 20)
    local z = math.random(-20, 20)
    createCoin(Vector3.new(x, 2, z))
end

Then you ask Claude to test it:

“Start play mode and tell me if the coins spawned correctly.”

Claude calls start_stop_play, waits, then reads get_console_output to report back.

Leaderboard with points

“Add a leaderboard that tracks each player’s coin count. Increment it when they pick up a coin.”

Claude updates the script to use leaderstats:

game.Players.PlayerAdded:Connect(function(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player

    local coins = Instance.new("IntValue")
    coins.Name = "Coins"
    coins.Value = 0
    coins.Parent = leaderstats
end)

And modifies the touch handler:

local player = game.Players:GetPlayerFromCharacter(character)
if player then
    player.leaderstats.Coins.Value += 1
    coin:Destroy()
end

Advanced: Agentic Workflows

At this level you stop giving Claude individual instructions and start giving it goals. Claude plans, executes multiple tool calls, reads results, and iterates — just like a junior developer would.

Generate a complete obstacle course

“Design a 10-stage obstacle course that goes from easy to hard. Each stage should have a different mechanic: moving platforms, lava floors, shrinking paths. Add checkpoints between stages.”

Claude breaks this into steps automatically:

  1. Calls run_code to clear the workspace baseline
  2. Generates each stage in a loop — different part configurations per stage
  3. Inserts SpawnPart checkpoints with TeleportService hooks
  4. Starts play mode via start_stop_play to verify pathability
  5. Reads get_console_output for any errors
  6. Patches broken stages and re-tests

No single prompt handles all of this — Claude chains tool calls until it is satisfied with the result.

Find and fix deprecated APIs

“Scan every Script and LocalScript in the game. List any that use deprecated Roblox APIs and rewrite them to use the modern equivalents.”

-- Claude uses run_code to enumerate scripts:
local function scanScripts(parent, results)
    for _, obj in ipairs(parent:GetDescendants()) do
        if obj:IsA("Script") or obj:IsA("LocalScript") then
            table.insert(results, {name = obj:GetFullName(), source = obj.Source})
        end
    end
end

local results = {}
scanScripts(game, results)
for _, s in ipairs(results) do
    print("===", s.name)
    print(s.source)
end

Claude reads the console output, identifies usages like game.Players.LocalPlayer.Character.Humanoid.Health (deprecated pattern), and rewrites each script in place with a follow-up run_code call.

AI-driven NPC behavior

“Create an NPC that patrols between 3 waypoints. If a player gets within 10 studs, the NPC chases them. If the player escapes to 30 studs, the NPC returns to patrol.”

Claude generates a full state-machine script:

local NPC = workspace.NPC  -- Claude inserts the NPC model first
local humanoid = NPC:FindFirstChild("Humanoid")
local rootPart = NPC:FindFirstChild("HumanoidRootPart")

local waypoints = {
    Vector3.new(0, 0, 0),
    Vector3.new(20, 0, 0),
    Vector3.new(20, 0, 20),
}
local currentWaypoint = 1
local state = "patrol"  -- "patrol" | "chase"
local CHASE_RANGE = 10
local ESCAPE_RANGE = 30

local function getNearestPlayer()
    local nearest, dist = nil, math.huge
    for _, p in ipairs(game.Players:GetPlayers()) do
        local char = p.Character
        if char and char:FindFirstChild("HumanoidRootPart") then
            local d = (char.HumanoidRootPart.Position - rootPart.Position).Magnitude
            if d < dist then nearest, dist = p, d end
        end
    end
    return nearest, dist
end

game:GetService("RunService").Heartbeat:Connect(function()
    local player, dist = getNearestPlayer()

    if state == "patrol" then
        if dist <= CHASE_RANGE then
            state = "chase"
        else
            humanoid:MoveTo(waypoints[currentWaypoint])
            if (rootPart.Position - waypoints[currentWaypoint]).Magnitude < 3 then
                currentWaypoint = (currentWaypoint % #waypoints) + 1
            end
        end
    elseif state == "chase" then
        if dist > ESCAPE_RANGE then
            state = "patrol"
        elseif player.Character then
            humanoid:MoveTo(player.Character.HumanoidRootPart.Position)
        end
    end
end)

After inserting the script Claude starts play mode, reads the console, and reports whether the NPC is moving correctly.


Useful Prompts Reference

GoalPrompt
Inspect scene”List every object in Workspace with its class and position.”
Quick prototype”Create a working door that opens when a player touches a button.”
Debug errors”Run the game, collect any errors from the console, and fix them.”
Bulk changes”Rename every Part named ‘Block’ to ‘Platform’ across the whole game.”
Performance audit”Find scripts using loops without task.wait() and add appropriate yields.”
Asset insertion”Insert a tree model from the Creator Store and place 20 copies randomly.”

Security Considerations

  • The MCP server runs locally — no game data leaves your machine to Roblox.
  • Your prompts do go to Anthropic’s servers (Claude’s API). Avoid sending passwords, API keys, or private user data in prompts.
  • Only connect trusted MCP clients (official Claude builds, Cursor). The server grants programmatic write access to your open place.
  • Roblox’s built-in MCP server respects Studio’s undo history — you can Ctrl+Z changes Claude makes.

Sources