roblox-mcp-pro 0.2.3 → 0.2.5
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/.agents/skills/roblox-mcp-pro/SKILL.md +99 -0
- package/.agents/skills/roblox-studio-plugin/SKILL.md +142 -0
- package/.agents/skills/roblox-ui-animation/SKILL.md +233 -0
- package/.agents/skills/roblox-ui-from-image/SKILL.md +138 -0
- package/README.md +7 -13
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/install-skills.d.ts +22 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +81 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/schemas/common.d.ts +0 -3
- package/dist/schemas/common.d.ts.map +1 -1
- package/dist/schemas/common.js +0 -6
- package/dist/schemas/common.js.map +1 -1
- package/dist/types.d.ts +0 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -6
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-mcp-pro
|
|
3
|
+
description: >-
|
|
4
|
+
How to drive the roblox-mcp-pro MCP server to control a live Roblox Studio session — running
|
|
5
|
+
Luau, querying/mutating the DataModel, building UI, terrain, lighting/effects, audio/animation,
|
|
6
|
+
spatial queries, and bidirectional Studio↔disk sync. Use this whenever the task involves the
|
|
7
|
+
roblox-mcp-pro tools (names like execute_luau, query_instances, mutate_instances, manage_sync,
|
|
8
|
+
manage_ui, manage_terrain, manage_lighting, spatial_query, batch_execute, workspace_state),
|
|
9
|
+
or whenever the user wants an agent to change things inside Roblox Studio through this server —
|
|
10
|
+
even if they don't name a specific tool. Read this before the first tool call to pick the right
|
|
11
|
+
tool and confirm the connection.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Using roblox-mcp-pro
|
|
15
|
+
|
|
16
|
+
roblox-mcp-pro exposes 23 tools that act on a **live Roblox Studio session**. The MCP server
|
|
17
|
+
talks to a Studio plugin over a localhost bridge; the plugin runs each command with full plugin
|
|
18
|
+
privileges and returns structured results. Your job is to choose the right tool and verify the
|
|
19
|
+
session is actually connected before relying on results.
|
|
20
|
+
|
|
21
|
+
## Always start with the connection check
|
|
22
|
+
|
|
23
|
+
Tools fail with `Error: Roblox Studio plugin is not connected…` if no Studio session is attached.
|
|
24
|
+
Before a sequence of changes, call **`system_info`** once. If `bridge.pluginConnected` is false,
|
|
25
|
+
tell the user to open Studio and click the **MCP** button on the toolbar (it must be highlighted),
|
|
26
|
+
and that the roblox-mcp-pro server must be running. Don't keep retrying blindly — surface the fix.
|
|
27
|
+
|
|
28
|
+
`workspace_state` is a good second call to orient yourself (place name, child counts per service,
|
|
29
|
+
camera, selection) before editing.
|
|
30
|
+
|
|
31
|
+
## Choosing the right tool
|
|
32
|
+
|
|
33
|
+
Prefer the **specific** tool over `execute_luau`. The structured tools return clean, validated
|
|
34
|
+
data and guard against damaging the session (they refuse to touch CoreGui/CorePackages). Reach for
|
|
35
|
+
`execute_luau` only for one-off logic no dedicated tool covers — and remember its output is
|
|
36
|
+
captured print/warn plus `return` values.
|
|
37
|
+
|
|
38
|
+
| You want to… | Use |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| Read instances / the tree | `query_instances` |
|
|
41
|
+
| Create / edit / move / clone / delete instances | `mutate_instances` |
|
|
42
|
+
| Read or write specific properties | `manage_properties` |
|
|
43
|
+
| Do many edits atomically (one undo) | `batch_execute` |
|
|
44
|
+
| Run arbitrary Luau | `execute_luau` |
|
|
45
|
+
| Find parts by space (box, radius, raycast, nearest) | `spatial_query` |
|
|
46
|
+
| Generate/edit terrain | `manage_terrain` |
|
|
47
|
+
| Time of day, fog, ambient, shadows | `manage_lighting` |
|
|
48
|
+
| Bloom/Blur/ColorCorrection/DoF/Atmosphere/Sky | `manage_effects` |
|
|
49
|
+
| Move/aim the camera | `manage_camera` |
|
|
50
|
+
| Animate properties over time | `manage_tween` |
|
|
51
|
+
| Physical props / welds | `manage_physics` |
|
|
52
|
+
| Build GUI (ScreenGui/Frame/labels/buttons) | `manage_ui` |
|
|
53
|
+
| Create/preview Sound | `manage_audio` |
|
|
54
|
+
| Animation instances / preview | `manage_animation` |
|
|
55
|
+
| Insert marketplace assets by id | `manage_assets` |
|
|
56
|
+
| Read/write script source, create scripts | `manage_scripts` |
|
|
57
|
+
| Mirror Studio↔disk both ways | `manage_sync` |
|
|
58
|
+
| Explorer selection | `manage_selection` |
|
|
59
|
+
| Studio version/theme/run state | `manage_studio` |
|
|
60
|
+
| Recent Output logs | `manage_logs` |
|
|
61
|
+
| High-level session snapshot | `workspace_state` |
|
|
62
|
+
|
|
63
|
+
## Patterns that work well
|
|
64
|
+
|
|
65
|
+
- **Batch related edits.** Building several parts or a whole structure? Wrap the operations in
|
|
66
|
+
`batch_execute` so it's one undo step and one round-trip, not N. Each step reports its own
|
|
67
|
+
ok/error, so a single bad op doesn't lose the rest.
|
|
68
|
+
- **Verify after you change.** After a `mutate_instances`/`manage_ui` create, a quick
|
|
69
|
+
`query_instances` (with `include_properties: true`) confirms it landed as intended.
|
|
70
|
+
- **Property value shapes.** Vectors are `[x,y,z]`; Color3 is `[r,g,b]` (0–1); UDim2 is
|
|
71
|
+
`[[xScale,xOffset],[yScale,yOffset]]`; CFrame is a 12-number array; enums and BrickColor are
|
|
72
|
+
their string names. Values are coerced to the property's current type, so match the existing type.
|
|
73
|
+
- **Check logs after running code.** If `execute_luau` or a playtest misbehaves, `manage_logs`
|
|
74
|
+
returns the recent Output history (newest first) so you can see prints and errors.
|
|
75
|
+
|
|
76
|
+
## Bidirectional sync (`manage_sync`)
|
|
77
|
+
|
|
78
|
+
`manage_sync` mirrors a Studio subtree to `roblox-mcp-sync/place_{placeId}/explorer/` and keeps
|
|
79
|
+
both sides in step, plus a `sourcemap.json` for luau-lsp. This is how you give the agent (and the
|
|
80
|
+
human) a stable on-disk copy of the project to edit with normal file tools.
|
|
81
|
+
|
|
82
|
+
- `action: "start"` — snapshot + begin two-way watching. Defaults to the script-bearing services
|
|
83
|
+
(ServerScriptService, ReplicatedStorage, StarterGui, StarterPlayer, ServerStorage). Pass
|
|
84
|
+
`roots: ["Workspace.Systems", …]` to scope differently.
|
|
85
|
+
- `action: "pull"` — force Studio → disk (after big structural changes).
|
|
86
|
+
- `action: "push"` — force disk → Studio (apply all local script edits).
|
|
87
|
+
- `action: "stop"` / `"status"`.
|
|
88
|
+
|
|
89
|
+
Script sync is fully two-way: creating, editing, or deleting a `.luau` file on disk creates,
|
|
90
|
+
updates, or deletes the script in Studio, and the same edits in Studio flow back to disk. The one
|
|
91
|
+
remaining gap is that non-script property changes in Studio mirror on resync (when instances are
|
|
92
|
+
added/removed), not live — use `pull` to resynchronize when in doubt.
|
|
93
|
+
|
|
94
|
+
## When something fails
|
|
95
|
+
|
|
96
|
+
Errors are returned as actionable text. Common ones: not connected (fix the plugin/server),
|
|
97
|
+
"protected service" (CoreGui/CorePackages are intentionally off-limits), and per-operation errors
|
|
98
|
+
inside `batch_execute`/`mutate_instances` results (other operations still ran). Read the `error`
|
|
99
|
+
string and adjust rather than retrying the same call.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-studio-plugin
|
|
3
|
+
description: >-
|
|
4
|
+
How to build, package, and install a high-quality Roblox Studio plugin in Luau — toolbar
|
|
5
|
+
buttons, dock widgets, the plugin lifecycle, persisting state across reloads, undo via
|
|
6
|
+
ChangeHistoryService, reading/writing script source via ScriptEditorService, HttpService from
|
|
7
|
+
plugins, and packaging to .rbxmx with Rojo. Use this whenever building or editing a Roblox
|
|
8
|
+
Studio plugin (anything under a plugin/ folder, a `*.server.luau` plugin entry, PluginToolbar,
|
|
9
|
+
PluginToolbarButton, CreateDockWidgetPluginGui, plugin:GetSetting/SetSetting, plugin:Activate),
|
|
10
|
+
or when the user wants to make, package, install, or debug a Studio plugin — even if they only
|
|
11
|
+
say "Studio plugin", "toolbar button", or "rbxmx". Read this before writing plugin code; it
|
|
12
|
+
encodes non-obvious Studio behaviors that are easy to get wrong.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Building Roblox Studio plugins
|
|
16
|
+
|
|
17
|
+
A Studio plugin is Luau that runs inside Studio's editor with elevated privileges (the global
|
|
18
|
+
`plugin` object). It can read/edit the DataModel, add toolbar UI, talk to localhost over HTTP,
|
|
19
|
+
and register undo waypoints. The hard part isn't the API surface — it's a handful of Studio
|
|
20
|
+
behaviors that silently misbehave if you don't know them. This skill front-loads those.
|
|
21
|
+
|
|
22
|
+
A complete, tested reference implementation lives in this repo under `plugin/` (entry
|
|
23
|
+
`plugin/src/init.server.luau`, modules in `plugin/src/`). Read it for a working example.
|
|
24
|
+
|
|
25
|
+
## Project layout (Rojo)
|
|
26
|
+
|
|
27
|
+
Structure the plugin as a directory that Rojo compiles into one `.rbxmx`:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
plugin/
|
|
31
|
+
default.project.json # { "name": "MyPlugin", "tree": { "$path": "src" } }
|
|
32
|
+
src/
|
|
33
|
+
init.server.luau # a folder with init.server.luau becomes the root *Script* (the plugin)
|
|
34
|
+
Foo.luau # sibling ModuleScripts → require(script.Foo)
|
|
35
|
+
Handlers/Bar.luau # subfolder → require(script.Handlers.Bar)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Build + install (Windows):
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
rojo build default.project.json --output MyPlugin.rbxmx
|
|
42
|
+
Copy-Item MyPlugin.rbxmx (Join-Path $env:LOCALAPPDATA "Roblox\Plugins\MyPlugin.rbxmx") -Force
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`%LOCALAPPDATA%\Roblox\Plugins` is the local plugins folder. Pin the Rojo version in `aftman.toml`
|
|
46
|
+
so builds are reproducible.
|
|
47
|
+
|
|
48
|
+
## Gotchas that will bite you
|
|
49
|
+
|
|
50
|
+
These are the non-obvious behaviors. Internalize them — each one is a bug we hit and fixed.
|
|
51
|
+
|
|
52
|
+
- **`PluginToolbarButton` has no settable `Text` or `Tooltip`.** The label/tooltip are fixed at
|
|
53
|
+
`CreateButton(id, tooltip, icon, text)` time. Assigning `button.Text = …` errors at runtime.
|
|
54
|
+
Show on/off state with `button:SetActive(bool)` (it highlights), not by changing text.
|
|
55
|
+
- **Studio does NOT reliably hot-reload local `.rbxmx` plugins.** After rebuilding, the running
|
|
56
|
+
plugin is often the *old* one until Studio restarts (or sometimes regains focus). Always log a
|
|
57
|
+
startup line like `("loaded %d tools"):format(n)` (and a version) so you can confirm in the
|
|
58
|
+
Output which build is actually live. When new code "isn't taking effect", suspect this first.
|
|
59
|
+
- **Plugin state resets every reload.** Persist anything that should survive a reload with
|
|
60
|
+
`plugin:SetSetting(key, value)` / `plugin:GetSetting(key)`. A common pattern: save whether the
|
|
61
|
+
plugin was "active", and on load auto-restore it so iterating doesn't require re-clicking.
|
|
62
|
+
- **One bad module breaks the whole plugin.** If `require(child)` errors at load, the requiring
|
|
63
|
+
module errors too, cascading to the entry script and the plugin silently does nothing. Load
|
|
64
|
+
optional/numerous modules defensively so a single failure disables only that feature:
|
|
65
|
+
```lua
|
|
66
|
+
local map = {}
|
|
67
|
+
for name, moduleName in pairs(HANDLER_NAMES) do
|
|
68
|
+
local ok, mod = pcall(require, Handlers[moduleName])
|
|
69
|
+
if ok then map[name] = mod else warn("failed to load " .. moduleName .. ": " .. tostring(mod)) end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
- **`loadstring` works in plugin context** (unlike in a running game without LoadStringEnabled),
|
|
73
|
+
but guard it: `if type(loadstring) ~= "function" then …`. Capture printed output by connecting
|
|
74
|
+
`LogService.MessageOut` for the duration of the call rather than trying to override `print`
|
|
75
|
+
(`setfenv` is unavailable in modern Luau).
|
|
76
|
+
|
|
77
|
+
## Toolbar + lifecycle skeleton
|
|
78
|
+
|
|
79
|
+
```lua
|
|
80
|
+
local toolbar = plugin:CreateToolbar("My Plugin")
|
|
81
|
+
-- label & tooltip are fixed here; state is shown via SetActive
|
|
82
|
+
local button = toolbar:CreateButton("MyToggle", "Toggle the thing", "", "My Plugin")
|
|
83
|
+
button.ClickableWhenViewportHidden = true
|
|
84
|
+
|
|
85
|
+
local active = false
|
|
86
|
+
local function setActive(v)
|
|
87
|
+
active = v
|
|
88
|
+
button:SetActive(v)
|
|
89
|
+
plugin:SetSetting("active", v) -- survive reloads
|
|
90
|
+
-- start/stop your work here
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
button.Click:Connect(function() setActive(not active) end)
|
|
94
|
+
plugin.Unloading:Connect(function() active = false end)
|
|
95
|
+
|
|
96
|
+
if plugin:GetSetting("active") == true then setActive(true) end -- auto-restore
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For richer UI, use a dock widget:
|
|
100
|
+
`plugin:CreateDockWidgetPluginGui(id, DockWidgetPluginGuiInfo.new(...))`, then parent a ScreenGui's
|
|
101
|
+
contents into it. Keep it lazy — only build the widget when first shown.
|
|
102
|
+
|
|
103
|
+
## Editing the DataModel safely
|
|
104
|
+
|
|
105
|
+
- **Wrap mutations in undo recordings** so the user can Ctrl+Z your changes:
|
|
106
|
+
```lua
|
|
107
|
+
local rec = ChangeHistoryService:TryBeginRecording("My action")
|
|
108
|
+
-- …make changes…
|
|
109
|
+
if rec then ChangeHistoryService:FinishRecording(rec, Enum.FinishRecordingOperation.Commit) end
|
|
110
|
+
```
|
|
111
|
+
Use `Cancel` instead of `Commit` when the operation failed, so a no-op doesn't pollute history.
|
|
112
|
+
- **Read/write script source via `ScriptEditorService`**, not just `.Source`. `GetEditorSource(s)`
|
|
113
|
+
reflects unsaved edits in an open document; `UpdateSourceAsync(s, fn)` writes safely even when
|
|
114
|
+
the script is open in the editor. Fall back to `s.Source` if the service call fails.
|
|
115
|
+
- **Never edit `CoreGui` / `CorePackages`** (and the plugin's own GUI service). Guard against it —
|
|
116
|
+
walking an instance's ancestry and refusing protected names prevents corrupting the session.
|
|
117
|
+
|
|
118
|
+
## HttpService from a plugin
|
|
119
|
+
|
|
120
|
+
Plugins can call localhost. `HttpService:RequestAsync` **yields**, so run loops in `task.spawn`.
|
|
121
|
+
A long-poll request blocks up to the server's hold time (well under the ~60s client timeout), so
|
|
122
|
+
it's fine for command/event channels. Best-effort enable with `pcall(function()
|
|
123
|
+
HttpService.HttpEnabled = true end)` and surface a clear message if requests fail (HTTP disabled or
|
|
124
|
+
server down) instead of spamming — warn once per outage and back off.
|
|
125
|
+
|
|
126
|
+
## Edit-mode caveats for common services
|
|
127
|
+
|
|
128
|
+
Most things work in edit mode, but a few are subtle:
|
|
129
|
+
- `TweenService:Create(…):Play()` advances in edit mode (Heartbeat runs) — usable for previews.
|
|
130
|
+
- Playing animations on a rig needs an `Animator`; visible motion of a programmatic
|
|
131
|
+
`LoadAnimation():Play()` may require Play mode.
|
|
132
|
+
- `Sound:Play()` previews in edit mode.
|
|
133
|
+
- `Selection` (`game:GetService("Selection")`) reads/sets the Explorer selection; great for
|
|
134
|
+
pointing the user at what you changed.
|
|
135
|
+
|
|
136
|
+
## Verifying a plugin
|
|
137
|
+
|
|
138
|
+
You usually can't fully drive Studio headlessly. Verify by: build + install, restart/reopen Studio,
|
|
139
|
+
confirm your startup log line appears (proves the new build loaded), click the toolbar button, and
|
|
140
|
+
watch the Output. If the plugin pairs with an external process (e.g. an MCP/localhost server), a
|
|
141
|
+
harness that starts the server and exercises it end-to-end while the real plugin answers is the
|
|
142
|
+
most reliable check — see this repo's test pattern.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-ui-animation
|
|
3
|
+
description: >-
|
|
4
|
+
How to animate a Roblox GUI through the roblox-mcp-pro tools — open/close transitions, slide-ins,
|
|
5
|
+
fades, scale "pops", button hover/press feedback, and reproducing a motion you see in a reference
|
|
6
|
+
video. Covers TweenService/TweenInfo, which GUI properties to tween (Size, Position, transparency,
|
|
7
|
+
UIScale, Rotation), easing choices, and a growing library of reusable motion patterns. Use this
|
|
8
|
+
whenever the task involves animating UI in Roblox Studio — phrases like "animate this popup",
|
|
9
|
+
"make the menu slide in", "add an open/close animation", "tween the panel", "button hover effect",
|
|
10
|
+
"match the animation in this clip/video", or whenever a UI should move/fade/scale rather than just
|
|
11
|
+
appear. Pairs with manage_ui (build the GUI) and manage_tween/execute_luau (drive the motion).
|
|
12
|
+
Read this before writing any UI tween.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Animating Roblox UI
|
|
16
|
+
|
|
17
|
+
UI motion in Roblox is almost always `TweenService` interpolating GUI properties over time. The
|
|
18
|
+
craft is picking **what** to tween, over **how long**, with **which easing**, so the result reads
|
|
19
|
+
the way the reference does. Build the static UI first (see the `roblox-ui-from-image` skill), then
|
|
20
|
+
layer motion on top.
|
|
21
|
+
|
|
22
|
+
Drive tweens with `execute_luau` (full control, can `:Wait()` on completion) or the `manage_tween`
|
|
23
|
+
tool for simple single-property tweens. Tweens **run in Studio edit mode** (Heartbeat advances), so
|
|
24
|
+
you can build and watch them without entering Play mode.
|
|
25
|
+
|
|
26
|
+
## Decode an animation from a reference video
|
|
27
|
+
|
|
28
|
+
When the user gives you a clip/video of the motion they want, don't guess from memory — **extract
|
|
29
|
+
frames and read the motion**. You can view images directly, so turn the video into images:
|
|
30
|
+
|
|
31
|
+
1. **Probe** it: `ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,duration -of default=noprint_wrappers=1 <file>`.
|
|
32
|
+
2. **Survey montage** — one image, whole clip at a glance, to find the animation windows and how
|
|
33
|
+
many cycles there are:
|
|
34
|
+
```bash
|
|
35
|
+
ffmpeg -v error -i <file> -vf "fps=3,scale=300:-1,tile=6x7" -frames:v 1 survey.png
|
|
36
|
+
```
|
|
37
|
+
3. **Zoom in** on one transition at high fps to read direction + easing frame-by-frame:
|
|
38
|
+
```bash
|
|
39
|
+
ffmpeg -v error -ss <start> -t <dur> -i <file> -vf "fps=15,scale=480:-1,tile=5x3" -frames:v 1 open.png
|
|
40
|
+
```
|
|
41
|
+
4. **Write the frames to a Windows-readable path** (e.g. `C:/Users/<you>/Downloads/_anim/`) so the
|
|
42
|
+
Read tool can open them — `/tmp` (bash) isn't visible to Read on Windows.
|
|
43
|
+
5. **Read the montages** and write a **spec**: what element moves, which property changes (size /
|
|
44
|
+
position / transparency / scale / rotation), start→end values, duration, easing
|
|
45
|
+
(accelerate-in / decelerate-out / overshoot), and any delay/stagger between elements.
|
|
46
|
+
|
|
47
|
+
Gotchas reading video: `drawtext` needs fontconfig (often missing) — skip frame-number overlays and
|
|
48
|
+
track frames by tile position instead. A montage tile is low-res; zoom tighter (higher `-ss`/`-t`,
|
|
49
|
+
fewer tiles, bigger `scale`) when you need to nail easing or see small elements.
|
|
50
|
+
|
|
51
|
+
State the spec back to the user and note easing values are estimates from the footage (tunable).
|
|
52
|
+
|
|
53
|
+
## Fundamentals
|
|
54
|
+
|
|
55
|
+
**TweenInfo** is the recipe: `TweenInfo.new(time, easingStyle, easingDirection, repeatCount,
|
|
56
|
+
reverses, delayTime)`.
|
|
57
|
+
|
|
58
|
+
- **Time**: UI snaps best at **0.15–0.35s**. Longer feels sluggish; shorter is imperceptible.
|
|
59
|
+
- **EasingStyle** — match the feel:
|
|
60
|
+
| Feel | Style / Direction |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| Smooth, neutral settle (default choice) | `Quad`/`Quint` + `Out` |
|
|
63
|
+
| Snappy, energetic settle | `Quint`/`Expo` + `Out` |
|
|
64
|
+
| Playful overshoot (pops past then back) | `Back` + `Out` |
|
|
65
|
+
| Bouncy landing | `Bounce` + `Out` |
|
|
66
|
+
| Elastic wobble | `Elastic` + `Out` |
|
|
67
|
+
| Symmetric in-and-out (e.g. a pulse) | `Sine` + `InOut` |
|
|
68
|
+
- `Out` decelerates into the end (best for things appearing). `In` accelerates (best for things
|
|
69
|
+
leaving). `InOut` for moves between two on-screen states.
|
|
70
|
+
|
|
71
|
+
**What to tween** (and how):
|
|
72
|
+
- **Size** — grow/shrink. For a "scale pop" prefer a child **`UIScale`** (`Scale` 0→1) over tweening
|
|
73
|
+
`Size` directly: it scales children too and won't fight layout. **A scale pivots around the
|
|
74
|
+
object's `AnchorPoint`** — set it to `[0.5,0.5]` for a symmetric pop from the center; an edge anchor
|
|
75
|
+
(e.g. `[1,0.5]`) makes it grow toward that edge, which looks lopsided. This applies to *every*
|
|
76
|
+
scale animation (panel open, hover pop): the anchor is the pivot. For an element that must stay
|
|
77
|
+
edge-pinned for layout, re-center it first (preserve its rect by reading `AbsolutePosition`/
|
|
78
|
+
`AbsoluteSize`) or scale an inner wrapper instead.
|
|
79
|
+
- **Position** — slide in/out. Tween a `UDim2`; pair with `AnchorPoint` so it slides from the right edge.
|
|
80
|
+
- **Transparency** — fade. `BackgroundTransparency` + `TextTransparency`/`ImageTransparency`. To fade
|
|
81
|
+
a whole panel as one unit, wrap it in a **`CanvasGroup`** and tween `GroupTransparency` (one value
|
|
82
|
+
fades everything, including strokes/gradients).
|
|
83
|
+
- **Rotation** — spin/settle (e.g. an icon, or a slight tilt-in).
|
|
84
|
+
- **UICorner / UIStroke.Thickness** — secondary polish.
|
|
85
|
+
|
|
86
|
+
**Sequencing / stagger**: animate list items with a small increasing `delayTime` (e.g. 0.04s × index)
|
|
87
|
+
so rows cascade instead of popping together — reads as "alive".
|
|
88
|
+
|
|
89
|
+
## Pattern library
|
|
90
|
+
|
|
91
|
+
### 1. Scale-pop panel + staggered children
|
|
92
|
+
|
|
93
|
+
The popup **scales up from its center** (a small box that grows to full size with a slight overshoot
|
|
94
|
+
"pop"); on close it scales back down. Its contents (cards/rows) then **fade in one by one** in a
|
|
95
|
+
cascade. This is the classic "juicy" game HUD popup — what most reference clips of menus opening show.
|
|
96
|
+
|
|
97
|
+
Two independent layers:
|
|
98
|
+
- **Panel scale** via a `UIScale` (`Scale` 0→1). The panel **must be center-anchored**
|
|
99
|
+
(`AnchorPoint = [0.5,0.5]`) so it grows from the middle — the anchor is the pivot of the scale.
|
|
100
|
+
`Back`/`Out` gives the overshoot pop.
|
|
101
|
+
- **Child stagger** via per-item **transparency** with an increasing `delayTime`. Fade — *not* Size
|
|
102
|
+
or UIScale — because items in a `UIListLayout` reflow when their `AbsoluteSize` changes, making the
|
|
103
|
+
list jump. Transparency doesn't touch layout.
|
|
104
|
+
|
|
105
|
+
```lua
|
|
106
|
+
local TweenService = game:GetService("TweenService")
|
|
107
|
+
local panel = script.Parent:WaitForChild("Panel")
|
|
108
|
+
panel.AnchorPoint = Vector2.new(0.5, 0.5) -- pivot of the scale = center
|
|
109
|
+
panel.Position = UDim2.new(0.5, 0, 0.5, 0)
|
|
110
|
+
local scale = Instance.new("UIScale"); scale.Parent = panel
|
|
111
|
+
|
|
112
|
+
local OPEN = TweenInfo.new(0.30, Enum.EasingStyle.Back, Enum.EasingDirection.Out)
|
|
113
|
+
local CLOSE = TweenInfo.new(0.20, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
|
|
114
|
+
|
|
115
|
+
-- every transparency-bearing prop of a subtree, so we can hide → fade it back in
|
|
116
|
+
local FADE = { BackgroundTransparency = true, TextTransparency = true, ImageTransparency = true }
|
|
117
|
+
local function faders(root)
|
|
118
|
+
local out, scan = {}, root:GetDescendants(); table.insert(scan, root)
|
|
119
|
+
for _, d in ipairs(scan) do
|
|
120
|
+
for p in pairs(FADE) do
|
|
121
|
+
local ok, v = pcall(function() return d[p] end)
|
|
122
|
+
if ok and typeof(v) == "number" and v < 1 then table.insert(out, { d, p, v }) end
|
|
123
|
+
end
|
|
124
|
+
if d:IsA("UIStroke") then table.insert(out, { d, "Transparency", d.Transparency }) end
|
|
125
|
+
end
|
|
126
|
+
return out
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
local rows = {} -- the cards, in layout order
|
|
130
|
+
for _, r in ipairs(panel.List:GetChildren()) do
|
|
131
|
+
if r:IsA("GuiObject") then table.insert(rows, r) end
|
|
132
|
+
end
|
|
133
|
+
table.sort(rows, function(a, b) return a.LayoutOrder < b.LayoutOrder end)
|
|
134
|
+
|
|
135
|
+
local function open()
|
|
136
|
+
scale.Scale = 0
|
|
137
|
+
for _, r in ipairs(rows) do
|
|
138
|
+
for _, f in ipairs(faders(r)) do f[1][f[2]] = 1 end -- hide cards
|
|
139
|
+
end
|
|
140
|
+
panel.Visible = true
|
|
141
|
+
TweenService:Create(scale, OPEN, { Scale = 1 }):Play() -- pop the panel
|
|
142
|
+
for i, r in ipairs(rows) do -- cascade the cards
|
|
143
|
+
local delay = (i - 1) * 0.05
|
|
144
|
+
for _, f in ipairs(faders(r)) do
|
|
145
|
+
TweenService:Create(f[1],
|
|
146
|
+
TweenInfo.new(0.22, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, delay),
|
|
147
|
+
{ [f[2]] = f[3] }):Play()
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
local function close()
|
|
153
|
+
return TweenService:Create(scale, CLOSE, { Scale = 0 }) -- :Play() then hide on .Completed
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The cards also *rise* a little as they fade in the reference. A true positional rise inside a
|
|
158
|
+
`UIListLayout` fights the layout — wrap each card in a `CanvasGroup` and tween `GroupTransparency`
|
|
159
|
+
plus an inner offset, rather than moving the card frame itself.
|
|
160
|
+
|
|
161
|
+
For **hover pop** on the buttons, the same pivot rule bites: a button anchored `[1,0.5]` pops toward
|
|
162
|
+
its right edge. Re-center it before adding the hover `UIScale`:
|
|
163
|
+
```lua
|
|
164
|
+
local function recenter(o) -- preserve the rect, move the anchor to the middle
|
|
165
|
+
local rel = o.AbsolutePosition - o.Parent.AbsolutePosition
|
|
166
|
+
o.AnchorPoint = Vector2.new(0.5, 0.5)
|
|
167
|
+
o.Position = UDim2.fromOffset(math.round(rel.X + o.AbsoluteSize.X/2),
|
|
168
|
+
math.round(rel.Y + o.AbsoluteSize.Y/2))
|
|
169
|
+
end -- run after layout settles (RunService.Heartbeat:Wait())
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 2. Clip-reveal panel (vertical "unroll")
|
|
173
|
+
|
|
174
|
+
The panel keeps full width and **grows in height from its top edge**; a `ClipsDescendants` container
|
|
175
|
+
reveals its contents top-to-bottom like unrolling a scroll. Close is the reverse. Snappy and clean —
|
|
176
|
+
common in game HUD popups.
|
|
177
|
+
|
|
178
|
+
**Critical rule — anchor every child to the TOP** (position by offset from the top edge). A child
|
|
179
|
+
anchored to the panel's *bottom* edge (`AnchorPoint.Y = 1`, `Position [[..],[1,..]]`) will **slide up
|
|
180
|
+
with the shrinking edge instead of being revealed in place**, breaking the illusion. Convert any
|
|
181
|
+
bottom-pinned child to a top offset at the same visual Y before animating.
|
|
182
|
+
|
|
183
|
+
```lua
|
|
184
|
+
local TweenService = game:GetService("TweenService")
|
|
185
|
+
|
|
186
|
+
-- one-time rig: clip + re-anchor to the top so a height tween unrolls downward.
|
|
187
|
+
local function setupReveal(panel)
|
|
188
|
+
local fullH = panel.Size.Y.Offset -- assumes a fixed-offset height
|
|
189
|
+
panel.ClipsDescendants = true
|
|
190
|
+
panel.AnchorPoint = Vector2.new(0.5, 0)
|
|
191
|
+
panel.Position = UDim2.new(0.5, 0, 0.5, -fullH / 2) -- same visual center as a centered panel
|
|
192
|
+
panel:SetAttribute("FullH", fullH)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
local OPEN = TweenInfo.new(0.32, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
|
|
196
|
+
local CLOSE = TweenInfo.new(0.26, Enum.EasingStyle.Quint, Enum.EasingDirection.In)
|
|
197
|
+
|
|
198
|
+
local function open(panel)
|
|
199
|
+
local h = panel:GetAttribute("FullH")
|
|
200
|
+
panel.Size = UDim2.new(panel.Size.X.Scale, panel.Size.X.Offset, 0, 0)
|
|
201
|
+
panel.Visible = true
|
|
202
|
+
TweenService:Create(panel, OPEN,
|
|
203
|
+
{ Size = UDim2.new(panel.Size.X.Scale, panel.Size.X.Offset, 0, h) }):Play()
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
local function close(panel)
|
|
207
|
+
local t = TweenService:Create(panel, CLOSE,
|
|
208
|
+
{ Size = UDim2.new(panel.Size.X.Scale, panel.Size.X.Offset, 0, 0) })
|
|
209
|
+
t.Completed:Connect(function() panel.Visible = false end)
|
|
210
|
+
t:Play()
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Optional flourish seen in reference clips: a **burst/sparkle** sprite that scales up and fades at the
|
|
215
|
+
center on open/close — an `ImageLabel` with a starburst asset, `UIScale` 0.5→1.4 + `ImageTransparency`
|
|
216
|
+
0→1 over ~0.25s. Needs a real `rbxassetid://` image.
|
|
217
|
+
|
|
218
|
+
<!-- Append new patterns here as they're decoded: scale-pop, slide-in, fade+blur backdrop, stagger list, etc. -->
|
|
219
|
+
|
|
220
|
+
## Gotchas
|
|
221
|
+
|
|
222
|
+
- **Verifying motion with a single screenshot is impossible.** `capture_studio` is one frame. To
|
|
223
|
+
sanity-check a transition, set the element to a **mid-state** (e.g. 45% of the way) and capture
|
|
224
|
+
that, or `:Wait()` on the tween and capture the settled end state. Tell the user to watch the live
|
|
225
|
+
tween in Studio for the motion itself.
|
|
226
|
+
- **`ui_preview` shows a clone.** Tweening the StarterGui original won't animate the preview overlay.
|
|
227
|
+
Hide the preview and run the tween on the real GUI (Studio renders StarterGui in the edit viewport).
|
|
228
|
+
- **ColorSequence (UIGradient.Color) can't be tweened by TweenService.** Tween `UIGradient.Offset` or
|
|
229
|
+
`Rotation` for motion, or step the color in a `RenderStepped`/`Heartbeat` loop.
|
|
230
|
+
- **Reuse, don't recreate.** Define `open`/`close` once (a ModuleScript or LocalScript) and call them;
|
|
231
|
+
don't rebuild tweens per click.
|
|
232
|
+
- **Clean up on close.** Disconnect `Completed`/input connections and set `Visible=false` after a
|
|
233
|
+
close tween so a hidden panel doesn't eat clicks.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: roblox-ui-from-image
|
|
3
|
+
description: >-
|
|
4
|
+
How to reproduce a UI design/mockup as a Roblox GUI through the roblox-mcp-pro tools — turning a
|
|
5
|
+
pasted screenshot, Figma export, or reference image into a pixel-close ScreenGui. Use whenever
|
|
6
|
+
the user supplies an image of a UI (HUD, menu, shop, popup, title screen, inventory, settings
|
|
7
|
+
panel) and wants it built in Roblox Studio, or says things like "make this UI", "build this
|
|
8
|
+
screen", "copy this layout", "match this design". Covers the build→preview→capture→compare→refine
|
|
9
|
+
loop with manage_ui, ui_preview, and capture_studio, plus UDim2/Color3 conventions and component
|
|
10
|
+
recipes. Read this before building UI from a reference.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Building Roblox UI from an image
|
|
14
|
+
|
|
15
|
+
Goal: given a reference image of a UI, produce a Roblox `ScreenGui` that looks like it. The win
|
|
16
|
+
comes from a **visual feedback loop** — build, look at the real render, compare to the mockup, fix
|
|
17
|
+
— not from one-shot guessing.
|
|
18
|
+
|
|
19
|
+
## The loop
|
|
20
|
+
|
|
21
|
+
1. **Get the image.** The user pastes/drags it into chat (you see it directly — no tool needed) or
|
|
22
|
+
gives a file path you can read. Study it before building.
|
|
23
|
+
2. **Analyze the design** (see checklist below) — structure, sizes, colors, fonts, spacing.
|
|
24
|
+
3. **Build** with `manage_ui` (`action: "create"`, `replace: true` so re-runs don't stack copies).
|
|
25
|
+
4. **Preview clean**: `ui_preview` (`action: "show"`, `path:` your ScreenGui) renders it full-screen
|
|
26
|
+
on a solid backdrop in edit mode (hides the 3D scene behind it).
|
|
27
|
+
5. **Capture**: `capture_studio` — now you see your UI isolated, centered in the viewport.
|
|
28
|
+
6. **Compare** the capture to the mockup. Note differences (position, size, color, font, spacing).
|
|
29
|
+
7. **Refine** with `manage_ui` (`action: "set"`) on the specific paths, then repeat 5–6 until close.
|
|
30
|
+
8. **Done**: `ui_preview` (`action: "hide"`) to remove the preview overlay.
|
|
31
|
+
|
|
32
|
+
Always `system_info` first to confirm the plugin is connected. Studio renders `StarterGui` content
|
|
33
|
+
in the edit viewport, so a capture without `ui_preview` also works — but `ui_preview` gives a clean
|
|
34
|
+
backdrop for accurate comparison.
|
|
35
|
+
|
|
36
|
+
## Analyze the mockup (before building)
|
|
37
|
+
|
|
38
|
+
- **Hierarchy**: outermost container(s) → panels → elements. Map to ScreenGui → Frame(s) → children.
|
|
39
|
+
- **Anchoring/position**: is it centered, corner-pinned, edge-docked? Pick `AnchorPoint` + `Position`
|
|
40
|
+
accordingly (center = `AnchorPoint [0.5,0.5]`, `Position [[0.5,0],[0.5,0]]`).
|
|
41
|
+
- **Sizing**: fixed px (offset) vs proportional (scale). Cards/buttons are usually offset; full-screen
|
|
42
|
+
dim layers are scale.
|
|
43
|
+
- **Colors**: background, panel, accent, text. Convert to `[r,g,b]` 0–1.
|
|
44
|
+
- **Text**: content, font (GothamBold/Gotham/etc.), size, alignment, color.
|
|
45
|
+
- **Corners/strokes/gradients**: rounded → `UICorner`; outline → `UIStroke`; gradient → `UIGradient`.
|
|
46
|
+
- **Repetition/lists**: rows/grids → `UIListLayout`/`UIGridLayout` instead of hand-placing each item.
|
|
47
|
+
|
|
48
|
+
## Conventions (roblox-mcp-pro property formats)
|
|
49
|
+
|
|
50
|
+
- `Size`/`Position` are **UDim2**: `[[xScale,xOffset],[yScale,yOffset]]`.
|
|
51
|
+
- Centered 360×200 card: `Size [[0,360],[0,200]]`, `AnchorPoint [0.5,0.5]`, `Position [[0.5,0],[0.5,0]]`.
|
|
52
|
+
- `BackgroundColor3`/`TextColor3` are **Color3** `[r,g,b]` 0–1 (e.g. `[0.12,0.14,0.2]`).
|
|
53
|
+
- `AnchorPoint` is `[x,y]` (Vector2, 0–1). `Font` is an enum name string (`"GothamBold"`).
|
|
54
|
+
- `UICorner.CornerRadius` is a **UDim**: `[scale,offset]` → `[0,16]` for 16px corners.
|
|
55
|
+
- Set `BorderSizePixel: 0` on Frames/buttons; `BackgroundTransparency: 1` on text-only labels.
|
|
56
|
+
|
|
57
|
+
## Component recipes
|
|
58
|
+
|
|
59
|
+
**Rounded panel**
|
|
60
|
+
```
|
|
61
|
+
{ className:"Frame", name:"Card",
|
|
62
|
+
properties:{ Size:[[0,360],[0,220]], AnchorPoint:[0.5,0.5], Position:[[0.5,0],[0.5,0]],
|
|
63
|
+
BackgroundColor3:[0.12,0.14,0.2], BorderSizePixel:0 },
|
|
64
|
+
children:[ { className:"UICorner", properties:{ CornerRadius:[0,16] } } ] }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Button (rounded, accent — with edge highlight + readable text)**
|
|
68
|
+
```
|
|
69
|
+
{ className:"TextButton", name:"Play",
|
|
70
|
+
properties:{ Text:"PLAY", Font:"GothamBold", TextSize:20, TextColor3:[1,1,1],
|
|
71
|
+
BackgroundColor3:[0.2,0.7,0.4], Size:[[0,160],[0,48]], BorderSizePixel:0 },
|
|
72
|
+
children:[ { className:"UICorner", properties:{ CornerRadius:[0,10] } },
|
|
73
|
+
// edge highlight — ApplyStrokeMode "Border" (see below); Color is a LIGHTER tint of
|
|
74
|
+
// the fill ([0.2,0.7,0.4] → [0.4,0.95,0.5]) so the button pops
|
|
75
|
+
{ className:"UIStroke", name:"Border",
|
|
76
|
+
properties:{ ApplyStrokeMode:"Border", Thickness:2, Color:[0.4,0.95,0.5] } },
|
|
77
|
+
// text outline — a SECOND stroke, Contextual + dark, so white text stays legible
|
|
78
|
+
{ className:"UIStroke", name:"TextStroke",
|
|
79
|
+
properties:{ ApplyStrokeMode:"Contextual", Thickness:2, Color:[0,0,0] } } ] }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Button with a gradient background (gradient must NOT tint the text)**
|
|
83
|
+
A `UIGradient` on a `TextButton` recolors the **text too**, not just the fill. To keep the gradient
|
|
84
|
+
on the button only, give the button empty `Text` and move the label into a child `TextLabel`.
|
|
85
|
+
Also: `UIGradient` **multiplies** with the object's own color, so set the fill `BackgroundColor3` to
|
|
86
|
+
**white `[1,1,1]`** — otherwise the gradient comes out tinted/darker than the colors you specified:
|
|
87
|
+
```
|
|
88
|
+
{ className:"TextButton", name:"Play",
|
|
89
|
+
properties:{ Text:"", BackgroundColor3:[1,1,1], Size:[[0,160],[0,48]], BorderSizePixel:0 },
|
|
90
|
+
children:[ { className:"UICorner", properties:{ CornerRadius:[0,10] } },
|
|
91
|
+
{ className:"UIGradient", properties:{ Rotation:90 } }, // set .Color via execute_luau
|
|
92
|
+
{ className:"UIStroke", name:"Border",
|
|
93
|
+
properties:{ ApplyStrokeMode:"Border", Thickness:2, Color:[0.4,0.95,0.5] } },
|
|
94
|
+
// text in its OWN label → the button's gradient can't reach it
|
|
95
|
+
{ className:"TextLabel", name:"Label",
|
|
96
|
+
properties:{ Text:"PLAY", Font:"GothamBold", TextSize:20, TextColor3:[1,1,1],
|
|
97
|
+
BackgroundTransparency:1, Size:[[1,0],[1,0]] },
|
|
98
|
+
children:[ { className:"UIStroke", properties:{ Thickness:2, Color:[0,0,0] } } ] } ] }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Vertical list (auto-stacked rows)**
|
|
102
|
+
```
|
|
103
|
+
{ className:"Frame", name:"List", properties:{ BackgroundTransparency:1, Size:[[1,0],[1,0]] },
|
|
104
|
+
children:[ { className:"UIListLayout",
|
|
105
|
+
properties:{ Padding:[0,8], FillDirection:"Vertical",
|
|
106
|
+
HorizontalAlignment:"Center", SortOrder:"LayoutOrder" } },
|
|
107
|
+
/* row frames here */ ] }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Notes & limits
|
|
111
|
+
|
|
112
|
+
- **One-call builds**: pass the whole nested `tree` to `manage_ui create` — don't create node-by-node.
|
|
113
|
+
- **Iterate with `set`**: once built, tweak single properties via `manage_ui set` on the exact path
|
|
114
|
+
(cheaper than rebuilding) — unless restructuring, then `create` with `replace:true`.
|
|
115
|
+
- **ImageLabel/ImageButton** need a real `Image` asset id (`rbxassetid://…`). If the mockup has
|
|
116
|
+
images you don't have ids for, build the layout with placeholder Frames and tell the user which
|
|
117
|
+
images to upload.
|
|
118
|
+
- **Gradients/sequences**: `UIGradient.Color` (ColorSequence) and similar sequence properties may not
|
|
119
|
+
coerce from simple arrays — set a solid color first, and use `execute_luau` for a true gradient if
|
|
120
|
+
needed. A `UIGradient` on a `TextButton`/`TextLabel` also **tints the text**, not just the fill —
|
|
121
|
+
to gradient the button only, give the button empty `Text` and move the label into a child
|
|
122
|
+
`TextLabel` with `BackgroundTransparency:1` (see the gradient-button recipe above).
|
|
123
|
+
- **UIGradient multiplies with the base color**: the gradient is *multiplied over* the object's own
|
|
124
|
+
color, so to get the exact ColorSequence colors you specified, first set the underlying property to
|
|
125
|
+
**white** — `BackgroundColor3:[1,1,1]` for a fill gradient, `TextColor3:[1,1,1]` for a text
|
|
126
|
+
gradient. A non-white base darkens/tints the whole gradient.
|
|
127
|
+
- **UIStroke on buttons/text — the two-stroke rule**: a `UIStroke` under a `TextButton`/`TextLabel`
|
|
128
|
+
defaults to `ApplyStrokeMode = "Contextual"`, which outlines the **text glyphs, not the button
|
|
129
|
+
border**. So a single default stroke gives you a text outline when you wanted an edge highlight.
|
|
130
|
+
- For a button **edge/glow highlight**, set `ApplyStrokeMode:"Border"`.
|
|
131
|
+
- **Highlight color = a lighter/whiter tint of the button's own fill**, not a contrasting accent.
|
|
132
|
+
Push the `BackgroundColor3` toward white (raise each channel, e.g. green fill `[0.2,0.7,0.4]` →
|
|
133
|
+
edge `[0.4,0.95,0.5]`) so the rim reads as a lit edge and the button visually pops off the panel.
|
|
134
|
+
- To get **both** an edge highlight *and* a crisp text outline, add **two** UIStrokes on the same
|
|
135
|
+
button: one `"Border"` (the lighter-tint edge) and one `"Contextual"` (the text). They target
|
|
136
|
+
different parts and both render.
|
|
137
|
+
- The **text** stroke should be a **dark/near-black** color so white button text stays legible.
|
|
138
|
+
- After previewing, always `ui_preview hide` so the overlay doesn't linger in CoreGui.
|
package/README.md
CHANGED
|
@@ -199,22 +199,16 @@ behavior, set the env var `ROBLOX_MCP_NO_PLUGIN_AUTOINSTALL=1`.)
|
|
|
199
199
|
Open Roblox Studio — a **Roblox MCP Pro** button appears in the toolbar. (Studio turns on HTTP
|
|
200
200
|
requests automatically when you connect; if needed, set `HttpService.HttpEnabled = true`.)
|
|
201
201
|
|
|
202
|
-
### Part 3 —
|
|
202
|
+
### Part 3 — Agent skills (automatic)
|
|
203
203
|
|
|
204
204
|
Skills are short guides that teach your AI how to use the tools well (building UI from an image,
|
|
205
|
-
animating GUIs, writing Studio plugins, etc.). **
|
|
206
|
-
|
|
205
|
+
animating GUIs, writing Studio plugins, etc.). **You don't need to do anything** — the server
|
|
206
|
+
installs them for you on startup, the same way it installs the plugin. Only **Claude Code**
|
|
207
|
+
(`~/.claude/skills`) and **Codex** (`~/.codex/skills`) have a skills mechanism, so skills are
|
|
208
|
+
copied there when those clients are present; other clients still work fine, just without the extra
|
|
209
|
+
guidance.
|
|
207
210
|
|
|
208
|
-
|
|
209
|
-
repo's `.agents/skills/` into your agent's skills directory:
|
|
210
|
-
|
|
211
|
-
| Agent | Skills folder |
|
|
212
|
-
|-------|----------------|
|
|
213
|
-
| Claude Code | `%USERPROFILE%\.claude\skills\` |
|
|
214
|
-
| Codex | `%USERPROFILE%\.codex\skills\` |
|
|
215
|
-
|
|
216
|
-
Skills to copy: `roblox-mcp-pro`, `roblox-ui-from-image`, `roblox-ui-animation`,
|
|
217
|
-
`roblox-studio-plugin`. Each ends up as `…\skills\<name>\SKILL.md`.
|
|
211
|
+
(To opt out, set `ROBLOX_MCP_NO_SKILL_AUTOINSTALL=1`.)
|
|
218
212
|
|
|
219
213
|
### Part 4 — Connect & verify
|
|
220
214
|
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { BRIDGE_HOST, BRIDGE_PORT } from "./constants.js";
|
|
|
16
16
|
import { resolveLicense } from "./licensing/license.js";
|
|
17
17
|
import { installLicenseGate } from "./licensing/gate.js";
|
|
18
18
|
import { installPlugin, ensurePluginInstalled } from "./install-plugin.js";
|
|
19
|
+
import { ensureSkillsInstalled } from "./install-skills.js";
|
|
19
20
|
import { VERSION } from "./version.js";
|
|
20
21
|
function log(message) {
|
|
21
22
|
process.stderr.write(`[roblox-mcp-pro] ${message}\n`);
|
|
@@ -43,6 +44,13 @@ async function main() {
|
|
|
43
44
|
log(`updated Studio plugin → restart Roblox Studio to load the new version`);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
47
|
+
// Keep agent skills up to date for skill-capable clients (Claude Code, Codex).
|
|
48
|
+
if (process.env.ROBLOX_MCP_NO_SKILL_AUTOINSTALL !== "1") {
|
|
49
|
+
const skills = await ensureSkillsInstalled();
|
|
50
|
+
if (skills.changed > 0) {
|
|
51
|
+
log(`installed/updated ${skills.changed} agent skill file(s)`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
46
54
|
const server = new McpServer({
|
|
47
55
|
name: "roblox-studio-mcp-server",
|
|
48
56
|
version: "0.1.0",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-skill distribution. The product skills (guides that teach an agent how
|
|
3
|
+
* to drive the tools well) ship inside this npm package. On server startup we
|
|
4
|
+
* copy them into the skill folders of any skill-capable client present on the
|
|
5
|
+
* machine, so a customer gets them with zero effort, the same way the Studio
|
|
6
|
+
* plugin self-installs.
|
|
7
|
+
*
|
|
8
|
+
* Only Claude Code (~/.claude/skills) and Codex (~/.codex/skills) have a skills
|
|
9
|
+
* mechanism today; other clients still work, just without the extra guidance.
|
|
10
|
+
*/
|
|
11
|
+
export interface SkillSyncResult {
|
|
12
|
+
/** Number of skill files newly written or updated. */
|
|
13
|
+
changed: number;
|
|
14
|
+
/** Skill-capable clients that received skills. */
|
|
15
|
+
clients: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Copy bundled skills into each present skill-capable client, writing only when
|
|
19
|
+
* a file is missing or different. Best-effort: never throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ensureSkillsInstalled(): Promise<SkillSyncResult>;
|
|
22
|
+
//# sourceMappingURL=install-skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-skills.d.ts","sourceRoot":"","sources":["../src/install-skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAsCH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,eAAe,CAAC,CAiCtE"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-skill distribution. The product skills (guides that teach an agent how
|
|
3
|
+
* to drive the tools well) ship inside this npm package. On server startup we
|
|
4
|
+
* copy them into the skill folders of any skill-capable client present on the
|
|
5
|
+
* machine, so a customer gets them with zero effort, the same way the Studio
|
|
6
|
+
* plugin self-installs.
|
|
7
|
+
*
|
|
8
|
+
* Only Claude Code (~/.claude/skills) and Codex (~/.codex/skills) have a skills
|
|
9
|
+
* mechanism today; other clients still work, just without the extra guidance.
|
|
10
|
+
*/
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { promises as fs } from "node:fs";
|
|
15
|
+
/** Product skills bundled in the package (must match package.json `files`). */
|
|
16
|
+
const SKILLS = [
|
|
17
|
+
"roblox-mcp-pro",
|
|
18
|
+
"roblox-studio-plugin",
|
|
19
|
+
"roblox-ui-animation",
|
|
20
|
+
"roblox-ui-from-image",
|
|
21
|
+
];
|
|
22
|
+
/** Skill-capable clients: parent dir that must exist → its skills/ root. */
|
|
23
|
+
function skillTargets() {
|
|
24
|
+
const h = os.homedir();
|
|
25
|
+
return [
|
|
26
|
+
{ parent: path.join(h, ".claude"), skillsDir: path.join(h, ".claude", "skills") },
|
|
27
|
+
{ parent: path.join(h, ".codex"), skillsDir: path.join(h, ".codex", "skills") },
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
function skillsSourceDir() {
|
|
31
|
+
// dist/install-skills.js → ../.agents/skills (package root; shipped by npm).
|
|
32
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
return path.join(here, "..", ".agents", "skills");
|
|
34
|
+
}
|
|
35
|
+
async function readIfPresent(p) {
|
|
36
|
+
try {
|
|
37
|
+
return await fs.readFile(p);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Copy bundled skills into each present skill-capable client, writing only when
|
|
45
|
+
* a file is missing or different. Best-effort: never throws.
|
|
46
|
+
*/
|
|
47
|
+
export async function ensureSkillsInstalled() {
|
|
48
|
+
const result = { changed: 0, clients: 0 };
|
|
49
|
+
const sourceDir = skillsSourceDir();
|
|
50
|
+
for (const target of skillTargets()) {
|
|
51
|
+
try {
|
|
52
|
+
// Only act on clients that are actually installed.
|
|
53
|
+
await fs.access(target.parent);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
result.clients++;
|
|
59
|
+
for (const skill of SKILLS) {
|
|
60
|
+
try {
|
|
61
|
+
const src = path.join(sourceDir, skill, "SKILL.md");
|
|
62
|
+
const source = await readIfPresent(src);
|
|
63
|
+
if (!source)
|
|
64
|
+
continue; // skill not in package; skip quietly
|
|
65
|
+
const destDir = path.join(target.skillsDir, skill);
|
|
66
|
+
const dest = path.join(destDir, "SKILL.md");
|
|
67
|
+
const existing = await readIfPresent(dest);
|
|
68
|
+
if (existing && existing.equals(source))
|
|
69
|
+
continue;
|
|
70
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
71
|
+
await fs.writeFile(dest, source);
|
|
72
|
+
result.changed++;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Skip this skill on error; keep going.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=install-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-skills.js","sourceRoot":"","sources":["../src/install-skills.ts"],"names":[],"mappings":"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"}
|
package/dist/schemas/common.d.ts
CHANGED
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
* Reusable Zod fragments shared across tool schemas.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { ResponseFormat } from "../types.js";
|
|
6
5
|
/** A dotted instance path rooted at `game`, e.g. "game.Workspace.Baseplate". */
|
|
7
6
|
export declare const InstancePath: z.ZodString;
|
|
8
7
|
/** A 3-number [x, y, z] vector. */
|
|
9
8
|
export declare const Vec3: z.ZodArray<z.ZodNumber, "many">;
|
|
10
|
-
/** Output format selector (markdown for humans, json for machines). */
|
|
11
|
-
export declare const responseFormat: z.ZodDefault<z.ZodNativeEnum<typeof ResponseFormat>>;
|
|
12
9
|
/** Standard pagination fragment. */
|
|
13
10
|
export declare const pagination: {
|
|
14
11
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/schemas/common.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/schemas/common.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAChF,eAAO,MAAM,YAAY,aAOtB,CAAC;AAEJ,mCAAmC;AACnC,eAAO,MAAM,IAAI,iCAG0B,CAAC;AAE5C,oCAAoC;AACpC,eAAO,MAAM,UAAU;;;CActB,CAAC"}
|
package/dist/schemas/common.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Reusable Zod fragments shared across tool schemas.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { ResponseFormat } from "../types.js";
|
|
6
5
|
/** A dotted instance path rooted at `game`, e.g. "game.Workspace.Baseplate". */
|
|
7
6
|
export const InstancePath = z
|
|
8
7
|
.string()
|
|
@@ -15,11 +14,6 @@ export const Vec3 = z
|
|
|
15
14
|
.array(z.number())
|
|
16
15
|
.length(3)
|
|
17
16
|
.describe("A 3-number [x, y, z] vector.");
|
|
18
|
-
/** Output format selector (markdown for humans, json for machines). */
|
|
19
|
-
export const responseFormat = z
|
|
20
|
-
.nativeEnum(ResponseFormat)
|
|
21
|
-
.default(ResponseFormat.MARKDOWN)
|
|
22
|
-
.describe("Output format: 'markdown' for human-readable text or 'json' for machine-readable.");
|
|
23
17
|
/** Standard pagination fragment. */
|
|
24
18
|
export const pagination = {
|
|
25
19
|
limit: z
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/schemas/common.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/schemas/common.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAChF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC;KAC1B,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,wBAAwB,CAAC;KAChC,GAAG,CAAC,GAAG,EAAE,qCAAqC,CAAC;KAC/C,QAAQ,CACP,2EAA2E;IACzE,2BAA2B,CAC9B,CAAC;AAEJ,mCAAmC;AACnC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC;KAClB,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;KACjB,MAAM,CAAC,CAAC,CAAC;KACT,QAAQ,CAAC,8BAA8B,CAAC,CAAC;AAE5C,oCAAoC;AACpC,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,GAAG,CAAC;SACR,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,2DAA2D,CAAC;IACxE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,wDAAwD,CAAC;CACtE,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -35,11 +35,6 @@ export interface BridgeStatus {
|
|
|
35
35
|
inflight: number;
|
|
36
36
|
lastPollAt: number | null;
|
|
37
37
|
}
|
|
38
|
-
/** Output format shared by many tools. */
|
|
39
|
-
export declare enum ResponseFormat {
|
|
40
|
-
MARKDOWN = "markdown",
|
|
41
|
-
JSON = "json"
|
|
42
|
-
}
|
|
43
38
|
/** A change event pushed from Studio -> server (used by sync). */
|
|
44
39
|
export interface StudioEvent {
|
|
45
40
|
/** Event kind, e.g. "added" | "removing" | "changed". */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uEAAuE;AACvE,MAAM,WAAW,OAAO;IACtB,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,OAAO,CAAC;IACd,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,gCAAgC;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uEAAuE;AACvE,MAAM,WAAW,OAAO;IACtB,6BAA6B;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,OAAO,CAAC;IACd,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,gCAAgC;IAChC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB"}
|
package/dist/types.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core type definitions shared across the MCP server and the Studio bridge.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
export var ResponseFormat;
|
|
6
|
-
(function (ResponseFormat) {
|
|
7
|
-
ResponseFormat["MARKDOWN"] = "markdown";
|
|
8
|
-
ResponseFormat["JSON"] = "json";
|
|
9
|
-
})(ResponseFormat || (ResponseFormat = {}));
|
|
4
|
+
export {};
|
|
10
5
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roblox-mcp-pro",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "MCP server that lets AI agents control Roblox Studio: live Luau execution, instance query/mutate, UI/terrain/lighting, and bidirectional Studio<->local sync. Paid subscription with a free trial.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Peerapol",
|
|
7
|
-
"homepage": "https://github.
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/PeerapolSelanon/roblox-mcp-pro.git"
|
|
11
|
-
},
|
|
7
|
+
"homepage": "https://peerapolselanon.github.io/roblox-mcp-pro-web/",
|
|
12
8
|
"bugs": {
|
|
13
|
-
"url": "
|
|
9
|
+
"url": "mailto:peerapolselanon@gmail.com"
|
|
14
10
|
},
|
|
15
11
|
"keywords": [
|
|
16
12
|
"roblox",
|
|
@@ -32,6 +28,10 @@
|
|
|
32
28
|
"plugin/src",
|
|
33
29
|
"plugin/default.project.json",
|
|
34
30
|
"plugin/RobloxMcpPro.rbxmx",
|
|
31
|
+
".agents/skills/roblox-mcp-pro",
|
|
32
|
+
".agents/skills/roblox-studio-plugin",
|
|
33
|
+
".agents/skills/roblox-ui-animation",
|
|
34
|
+
".agents/skills/roblox-ui-from-image",
|
|
35
35
|
"README.md",
|
|
36
36
|
"LICENSE"
|
|
37
37
|
],
|