sbox-mcp-server 1.3.2 → 1.5.0
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/README.md +131 -113
- package/dist/index.js +34 -1
- package/dist/tools/characters.d.ts +4 -0
- package/dist/tools/characters.js +209 -0
- package/dist/tools/diagnostics.d.ts +4 -0
- package/dist/tools/diagnostics.js +325 -0
- package/dist/tools/docs.d.ts +4 -0
- package/dist/tools/docs.js +334 -0
- package/dist/tools/leveltools.d.ts +4 -0
- package/dist/tools/leveltools.js +148 -0
- package/dist/tools/navigation.d.ts +4 -0
- package/dist/tools/navigation.js +51 -0
- package/dist/tools/networking.js +10 -8
- package/dist/tools/objecttools.d.ts +4 -0
- package/dist/tools/objecttools.js +106 -0
- package/dist/tools/physics.js +22 -0
- package/dist/tools/project.js +12 -2
- package/dist/tools/visuals.d.ts +4 -0
- package/dist/tools/visuals.js +289 -0
- package/dist/transport/bridge-client.js +27 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,113 +1,131 @@
|
|
|
1
|
-
# sbox-mcp-server
|
|
2
|
-
|
|
3
|
-
MCP Server for the s&box game engine. Lets Claude Code build s&box games through conversation —
|
|
4
|
-
|
|
5
|
-
## Fastest install — the Claude Code plugin
|
|
6
|
-
|
|
7
|
-
If you use Claude Code, the easiest install is the companion plugin. It registers this MCP server automatically, ships a workflow skill, and includes the `sbox-game-dev` specialist agent.
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
/plugin marketplace add LouSputthole/Sbox-Claude
|
|
11
|
-
/plugin install sbox-claude
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
You still need to install the s&box-side **bridge addon** into your project's `Libraries/` folder (see step 1 below). The plugin handles the Claude side; the addon handles the s&box side.
|
|
15
|
-
|
|
16
|
-
## Manual install — three steps
|
|
17
|
-
|
|
18
|
-
### 1. Install the bridge addon in s&box
|
|
19
|
-
|
|
20
|
-
The bridge addon runs inside the s&box editor and receives commands from this MCP server. It MUST live inside a project's `Libraries/` folder — putting it in s&box's global `addons/` will silently fail to compile.
|
|
21
|
-
|
|
22
|
-
```powershell
|
|
23
|
-
git clone https://github.com/LouSputthole/Sbox-Claude.git
|
|
24
|
-
cd Sbox-Claude
|
|
25
|
-
.\install.ps1 -RemoveStaleAddons # Windows, auto-detects your s&box project
|
|
26
|
-
./install.sh --remove-stale # Linux/Mac/WSL
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
See [INSTALL.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/INSTALL.md) for the full guide and manual fallback.
|
|
30
|
-
|
|
31
|
-
### 2. Register the MCP server with Claude Code
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
claude mcp add sbox -- npx sbox-mcp-server
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
This is the bare command — equivalent to what the plugin's `.mcp.json` does for you.
|
|
38
|
-
|
|
39
|
-
### 3. Open s&box
|
|
40
|
-
|
|
41
|
-
Open your project. The bridge starts automatically. Verify with:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
Check the bridge status.
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
You should see `connected: true, handlerCount:
|
|
48
|
-
|
|
49
|
-
## How it works
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
Claude Code → (stdio) → sbox-mcp-server → (file IPC) → bridge addon → s&box editor
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Communication uses file-based IPC through `%TEMP%/sbox-bridge-ipc/`. The MCP server writes request JSON files, the bridge addon (running inside s&box) polls and processes on the main editor thread, then writes response files back. WebSocket is not used — s&box's sandboxed C# environment blocks `System.Net`.
|
|
56
|
-
|
|
57
|
-
## Tools (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
|
|
63
|
-
| **
|
|
64
|
-
| **
|
|
65
|
-
| **
|
|
66
|
-
| **
|
|
67
|
-
| **
|
|
68
|
-
| **
|
|
69
|
-
| **
|
|
70
|
-
| **
|
|
71
|
-
| **
|
|
72
|
-
| **
|
|
73
|
-
| **
|
|
74
|
-
| **
|
|
75
|
-
| **
|
|
76
|
-
| **
|
|
77
|
-
| **
|
|
78
|
-
| **
|
|
79
|
-
| **
|
|
80
|
-
| **
|
|
81
|
-
| **
|
|
82
|
-
| **
|
|
83
|
-
| **
|
|
84
|
-
| **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
1
|
+
# sbox-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP Server for the s&box game engine. Lets Claude Code build s&box games through conversation — **150 tools / 142 editor handlers** for scenes, scripts, GameObjects, components, assets, materials, audio, physics, UI, networking, publishing, world-gen, lighting & atmosphere, characters, scene layout, navmesh & spatial queries, particles, self-diagnosis, console/C# execution, live docs search, and type discovery.
|
|
4
|
+
|
|
5
|
+
## Fastest install — the Claude Code plugin
|
|
6
|
+
|
|
7
|
+
If you use Claude Code, the easiest install is the companion plugin. It registers this MCP server automatically, ships a workflow skill, and includes the `sbox-game-dev` specialist agent.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
/plugin marketplace add LouSputthole/Sbox-Claude
|
|
11
|
+
/plugin install sbox-claude
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
You still need to install the s&box-side **bridge addon** into your project's `Libraries/` folder (see step 1 below). The plugin handles the Claude side; the addon handles the s&box side.
|
|
15
|
+
|
|
16
|
+
## Manual install — three steps
|
|
17
|
+
|
|
18
|
+
### 1. Install the bridge addon in s&box
|
|
19
|
+
|
|
20
|
+
The bridge addon runs inside the s&box editor and receives commands from this MCP server. It MUST live inside a project's `Libraries/` folder — putting it in s&box's global `addons/` will silently fail to compile.
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
git clone https://github.com/LouSputthole/Sbox-Claude.git
|
|
24
|
+
cd Sbox-Claude
|
|
25
|
+
.\install.ps1 -RemoveStaleAddons # Windows, auto-detects your s&box project
|
|
26
|
+
./install.sh --remove-stale # Linux/Mac/WSL
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
See [INSTALL.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/INSTALL.md) for the full guide and manual fallback.
|
|
30
|
+
|
|
31
|
+
### 2. Register the MCP server with Claude Code
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
claude mcp add sbox -- npx sbox-mcp-server
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This is the bare command — equivalent to what the plugin's `.mcp.json` does for you.
|
|
38
|
+
|
|
39
|
+
### 3. Open s&box
|
|
40
|
+
|
|
41
|
+
Open your project. The bridge starts automatically. Verify with:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Check the bridge status.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You should see `connected: true, handlerCount: 142`. (That's the editor-side handler count; the server exposes 150 tools total — a handful run MCP-server-side and need no editor handler.)
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Claude Code → (stdio) → sbox-mcp-server → (file IPC) → bridge addon → s&box editor
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Communication uses file-based IPC through `%TEMP%/sbox-bridge-ipc/`. The MCP server writes request JSON files, the bridge addon (running inside s&box) polls and processes on the main editor thread, then writes response files back. WebSocket is not used — s&box's sandboxed C# environment blocks `System.Net`.
|
|
56
|
+
|
|
57
|
+
## Tools (150 / 142 editor handlers — v1.5.0)
|
|
58
|
+
|
|
59
|
+
`get_bridge_status` reports `handlerCount: 142` — that's the C# handlers compiled inside the editor. Six tools run **MCP-server-side** and need no editor handler: `read_log`, `get_compile_errors`, `execute_csharp`, `search_docs`, `get_doc_page`, `list_doc_categories`. They read the log / hotload-eval / fetch docs directly, so they keep working even when the editor has crashed or stalled.
|
|
60
|
+
|
|
61
|
+
| Category | Tools |
|
|
62
|
+
|----------|-------|
|
|
63
|
+
| **Project** | get_project_info, list_project_files, read_file, write_file |
|
|
64
|
+
| **Scripts** | create_script, edit_script, delete_script, trigger_hotload |
|
|
65
|
+
| **Scenes** | list_scenes, load_scene, save_scene, create_scene |
|
|
66
|
+
| **GameObjects** | create/delete/duplicate/rename, set_parent/enabled/transform |
|
|
67
|
+
| **Components** | get/set_property, get_all_properties, list_available, add_component, set_prefab_ref |
|
|
68
|
+
| **Hierarchy** | get_scene_hierarchy (with `maxDepth` + `rootId`), get/select/focus_object |
|
|
69
|
+
| **Assets** | search_assets, list_asset_library, install_asset, get_asset_info |
|
|
70
|
+
| **Materials** | assign_model, create/assign_material, set_material_property |
|
|
71
|
+
| **Audio** | list_sounds, create_sound_event, assign_sound, play_sound_preview |
|
|
72
|
+
| **Play Mode** | start/stop_play, is_playing |
|
|
73
|
+
| **Runtime** | get/set_runtime_property, take_screenshot |
|
|
74
|
+
| **Editor** | undo, redo |
|
|
75
|
+
| **Prefabs** | create/instantiate_prefab, list_prefabs, get_prefab_info |
|
|
76
|
+
| **Physics** | add_physics, add_collider, add_joint, raycast |
|
|
77
|
+
| **UI** | create_razor_ui, add_screen_panel, add_world_panel |
|
|
78
|
+
| **Templates** | create_player/npc_controller, create_game_manager, create_trigger_zone |
|
|
79
|
+
| **Networking** | network_helper, configure/status, spawn, ownership, sync, RPCs, lobby/event templates |
|
|
80
|
+
| **Publishing** | project_config, validate, thumbnail, package_details |
|
|
81
|
+
| **World gen** | invoke_button, list_component_buttons, raycast_terrain, build_terrain_mesh |
|
|
82
|
+
| **Map edit** | add_terrain_hill/clearing/trail, clear_terrain_features, sculpt_terrain |
|
|
83
|
+
| **Caves / Forest** | add_cave_waypoint, clear_cave_path, add_forest_poi/trail, set_forest_seed, clear_forest_pois, paint_forest_density |
|
|
84
|
+
| **Placement** | place_along_path |
|
|
85
|
+
| **Discovery** | describe_type, search_types, get_method_signature, find_in_project |
|
|
86
|
+
| **Status** | get_bridge_status |
|
|
87
|
+
| **Visual & atmosphere** *(v1.4.0)* | add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look |
|
|
88
|
+
| **Characters** *(v1.4.0)* | spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression |
|
|
89
|
+
| **Scene & level** *(v1.4.0)* | snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance |
|
|
90
|
+
| **Environment** *(v1.4.0)* | scatter_props, randomize_transforms, group_objects |
|
|
91
|
+
| **Object utilities** *(v1.4.0)* | find_objects, set_tint, replace_model, set_tags |
|
|
92
|
+
| **VFX** *(v1.4.0, experimental)* | spawn_particle, create_particle_effect, add_trail, add_beam — compile but do **not** render through the bridge; use `spawn_vpcf` (below) for visible particles |
|
|
93
|
+
| **Diagnostics** *(v1.5.0, MCP-server-side)* | read_log, get_compile_errors — read `sbox-dev.log` directly; work even when the editor has crashed |
|
|
94
|
+
| **Camera** *(v1.5.0)* | screenshot_from (**aim a shot at any object/point** — `take_screenshot` is fixed to the Main Camera), frame_camera (move the editor viewport) |
|
|
95
|
+
| **Navigation** *(v1.5.0)* | bake_navmesh, get_navmesh_path |
|
|
96
|
+
| **Spatial** *(v1.5.0)* | physics_overlap (volume counterpart to raycast) |
|
|
97
|
+
| **Reflections** *(v1.5.0)* | bake_reflections (a placed EnvmapProbe captures nothing until baked) |
|
|
98
|
+
| **Particles** *(v1.5.0)* | spawn_vpcf — compiled `.vpcf` via LegacyParticleSystem, the **supported** particle path |
|
|
99
|
+
| **Console / Exec** *(v1.5.0)* | console_run, execute_csharp *(experimental)* |
|
|
100
|
+
| **Object utilities** *(v1.5.0)* | remove_component, get_tags |
|
|
101
|
+
| **Docs search** *(v1.5.0, MCP-server-side)* | search_docs, get_doc_page, list_doc_categories — official `Facepunch/sbox-docs` |
|
|
102
|
+
|
|
103
|
+
## Working with Claude effectively
|
|
104
|
+
|
|
105
|
+
Three disciplines prevent the iteration-loop trap:
|
|
106
|
+
|
|
107
|
+
1. **After visual changes, see the result — and aim the camera.** `take_screenshot` renders from the scene's Main Camera (one fixed angle), so it often won't show the thing you just changed. Use **`screenshot_from`** to point the camera at the target object/point, then read the PNG. Claude is a multimodal model — guessing about visual outcomes from code alone produces long iteration loops.
|
|
108
|
+
2. **Before writing code that touches an unfamiliar s&box type, call `describe_type` or `search_types`.** Reflection is the source of truth; training data goes stale across SDK versions.
|
|
109
|
+
3. **When something breaks, read the log instead of guessing.** `get_compile_errors` surfaces the latest C# compile failures and `read_log` tails `sbox-dev.log` — both MCP-server-side, so they work even if the editor crashed.
|
|
110
|
+
|
|
111
|
+
The companion plugin's `sbox-build-feature` skill encodes this workflow plus the common gotchas. If you're not using the plugin, the same rules apply manually.
|
|
112
|
+
|
|
113
|
+
## Requirements
|
|
114
|
+
|
|
115
|
+
- **Node.js 18+**
|
|
116
|
+
- **s&box** with the bridge addon installed in your project's `Libraries/` folder
|
|
117
|
+
- **Claude Code**
|
|
118
|
+
|
|
119
|
+
## Documentation
|
|
120
|
+
|
|
121
|
+
- [Main README](https://github.com/LouSputthole/Sbox-Claude/blob/main/README.md) — full project overview
|
|
122
|
+
- [INSTALL.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/INSTALL.md) — install + manual fallback
|
|
123
|
+
- [TROUBLESHOOTING.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/TROUBLESHOOTING.md) — common failures and fixes
|
|
124
|
+
- [CHANGELOG.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/CHANGELOG.md) — release history
|
|
125
|
+
- [Plugin README](https://github.com/LouSputthole/Sbox-Claude/blob/main/plugins/sbox-claude/README.md) — Claude Code plugin docs
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
**GPL-3.0** — see [LICENSE](../LICENSE) for details.
|
|
130
|
+
|
|
131
|
+
Copyright (c) 2026 [sboxskins.gg](https://sboxskins.gg)
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,13 @@ import { registerNetworkingTools } from "./tools/networking.js";
|
|
|
33
33
|
import { registerPublishingTools } from "./tools/publishing.js";
|
|
34
34
|
import { registerWorldTools } from "./tools/world.js";
|
|
35
35
|
import { registerDiscoveryTools } from "./tools/discovery.js";
|
|
36
|
+
import { registerVisualTools } from "./tools/visuals.js";
|
|
37
|
+
import { registerCharacterTools } from "./tools/characters.js";
|
|
38
|
+
import { registerLevelTools } from "./tools/leveltools.js";
|
|
39
|
+
import { registerObjectTools } from "./tools/objecttools.js";
|
|
40
|
+
import { registerDiagnosticTools } from "./tools/diagnostics.js";
|
|
41
|
+
import { registerDocsTools } from "./tools/docs.js";
|
|
42
|
+
import { registerNavigationTools } from "./tools/navigation.js";
|
|
36
43
|
// ── CLI flags ──────────────────────────────────────────────────────
|
|
37
44
|
const args = process.argv.slice(2);
|
|
38
45
|
/** Read the package version from package.json, or return "unknown" on failure. */
|
|
@@ -68,7 +75,7 @@ ENVIRONMENT VARIABLES
|
|
|
68
75
|
CONNECT TO CLAUDE CODE
|
|
69
76
|
claude mcp add sbox -- node /path/to/sbox-mcp-server/dist/index.js
|
|
70
77
|
|
|
71
|
-
TOOLS (
|
|
78
|
+
TOOLS (150 total / 142 s&box-editor handlers — +16 in v1.5.0)
|
|
72
79
|
Project: get_project_info, list_project_files, read_file, write_file
|
|
73
80
|
Scripts: create_script, edit_script, delete_script, trigger_hotload
|
|
74
81
|
Scenes: list_scenes, load_scene, save_scene, create_scene
|
|
@@ -95,6 +102,25 @@ TOOLS (99 working — was 109; 10 unimplementable tools removed in v1.3.0)
|
|
|
95
102
|
Placement: place_along_path
|
|
96
103
|
Discovery: describe_type, search_types, get_method_signature, find_in_project
|
|
97
104
|
Status: get_bridge_status
|
|
105
|
+
|
|
106
|
+
── New in v1.4.0 ───────────────────────────────────
|
|
107
|
+
Visual: add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look
|
|
108
|
+
Characters: spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression
|
|
109
|
+
Scene: snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance
|
|
110
|
+
Environment: scatter_props, randomize_transforms, group_objects
|
|
111
|
+
Utilities: find_objects, set_tint, replace_model, set_tags
|
|
112
|
+
VFX (exp): spawn_particle, create_particle_effect, add_trail, add_beam
|
|
113
|
+
|
|
114
|
+
── New in v1.5.0 ───────────────────────────────────
|
|
115
|
+
Diagnostics: read_log, get_compile_errors (MCP-server-side — work even if the editor crashed)
|
|
116
|
+
Camera: screenshot_from (AIM a shot at an object/point — take_screenshot is fixed to the Main Camera), frame_camera
|
|
117
|
+
Navigation: bake_navmesh, get_navmesh_path
|
|
118
|
+
Spatial: physics_overlap (volume counterpart to raycast)
|
|
119
|
+
Reflections: bake_reflections
|
|
120
|
+
Particles: spawn_vpcf (compiled .vpcf via LegacyParticleSystem — the supported particle path)
|
|
121
|
+
Console/Exec: console_run, execute_csharp (experimental)
|
|
122
|
+
Object utils: remove_component, get_tags
|
|
123
|
+
Docs search: search_docs, get_doc_page, list_doc_categories (MCP-server-side — official Facepunch/sbox-docs)
|
|
98
124
|
`);
|
|
99
125
|
process.exit(0);
|
|
100
126
|
}
|
|
@@ -148,6 +174,13 @@ registerNetworkingTools(server, bridge);
|
|
|
148
174
|
registerPublishingTools(server, bridge);
|
|
149
175
|
registerWorldTools(server, bridge);
|
|
150
176
|
registerDiscoveryTools(server, bridge);
|
|
177
|
+
registerVisualTools(server, bridge);
|
|
178
|
+
registerCharacterTools(server, bridge);
|
|
179
|
+
registerLevelTools(server, bridge);
|
|
180
|
+
registerObjectTools(server, bridge);
|
|
181
|
+
registerDiagnosticTools(server, bridge);
|
|
182
|
+
registerDocsTools(server, bridge);
|
|
183
|
+
registerNavigationTools(server, bridge);
|
|
151
184
|
/** Start the MCP server on stdio and attempt initial Bridge connection. */
|
|
152
185
|
async function main() {
|
|
153
186
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Character & model tools (Batch 19): spawn world models, spawn animated
|
|
4
|
+
* Citizen characters, dress them with clothing, toggle bodygroups, and pose
|
|
5
|
+
* them via CitizenAnimationHelper.
|
|
6
|
+
*
|
|
7
|
+
* Unlike particles (Batch 18), everything here is a STATIC mesh/pose that
|
|
8
|
+
* renders in the editor viewport — so the screenshot loop works: after a
|
|
9
|
+
* change, take_screenshot and read the result. Animation *playback* is
|
|
10
|
+
* runtime, but poses preview in-editor via PlayAnimationsInEditorScene.
|
|
11
|
+
*/
|
|
12
|
+
const ColorSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
r: z.number().min(0).describe("Red, 0-1"),
|
|
15
|
+
g: z.number().min(0).describe("Green, 0-1"),
|
|
16
|
+
b: z.number().min(0).describe("Blue, 0-1"),
|
|
17
|
+
a: z.number().min(0).max(1).optional().describe("Alpha, 0-1 (default 1)"),
|
|
18
|
+
})
|
|
19
|
+
.describe("RGBA colour as 0-1 floats (model tint)");
|
|
20
|
+
const Vector3Schema = z
|
|
21
|
+
.object({ x: z.number(), y: z.number(), z: z.number() })
|
|
22
|
+
.describe("World position {x,y,z}");
|
|
23
|
+
const RotationSchema = z
|
|
24
|
+
.object({ pitch: z.number(), yaw: z.number(), roll: z.number() })
|
|
25
|
+
.describe("Rotation {pitch,yaw,roll} in degrees");
|
|
26
|
+
export function registerCharacterTools(server, bridge) {
|
|
27
|
+
// ── spawn_model ────────────────────────────────────────────────────
|
|
28
|
+
server.tool("spawn_model", "Spawn a GameObject with a ModelRenderer showing a model — the quick way to place a prop. Pass an engine/project model path (e.g. 'models/citizen/citizen.vmdl', 'models/dev/box.vmdl'). Cloud (sbox.game) models must be installed first via install_asset, then pass the resulting path. Add physics separately with add_collider/add_physics if needed.", {
|
|
29
|
+
model: z
|
|
30
|
+
.string()
|
|
31
|
+
.describe("Model path, e.g. 'models/dev/box.vmdl' or an installed model path"),
|
|
32
|
+
name: z.string().optional().describe("GameObject name"),
|
|
33
|
+
position: Vector3Schema.optional().describe("World position"),
|
|
34
|
+
rotation: RotationSchema.optional().describe("World rotation"),
|
|
35
|
+
scale: Vector3Schema.optional().describe("World scale (default 1,1,1)"),
|
|
36
|
+
tint: ColorSchema.optional().describe("Model tint colour"),
|
|
37
|
+
parentId: z.string().optional().describe("GUID of a parent GameObject"),
|
|
38
|
+
}, async (params) => {
|
|
39
|
+
const res = await bridge.send("spawn_model", params);
|
|
40
|
+
if (!res.success) {
|
|
41
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
// ── spawn_citizen ──────────────────────────────────────────────────
|
|
48
|
+
server.tool("spawn_citizen", "Spawn an animated Citizen character: a SkinnedModelRenderer with the Citizen model, plus (by default) a CitizenAnimationHelper so it idles. PlayAnimationsInEditorScene is enabled so the idle pose shows in the editor view (screenshot-verifiable). Dress it afterward with dress_citizen, pose it with pose_citizen.", {
|
|
49
|
+
name: z.string().optional().describe("GameObject name (default 'Citizen')"),
|
|
50
|
+
model: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Override the skinned model (default 'models/citizen/citizen.vmdl')"),
|
|
54
|
+
position: Vector3Schema.optional().describe("World position"),
|
|
55
|
+
rotation: RotationSchema.optional().describe("World rotation"),
|
|
56
|
+
scale: Vector3Schema.optional().describe("World scale (default 1,1,1)"),
|
|
57
|
+
tint: ColorSchema.optional().describe("Body tint colour"),
|
|
58
|
+
animator: z
|
|
59
|
+
.boolean()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Add a CitizenAnimationHelper for idle/pose (default true)"),
|
|
62
|
+
holdType: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Initial hold pose, e.g. None, Pistol, Rifle, Shotgun, HoldItem, Punch, Swing"),
|
|
66
|
+
moveStyle: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Movement style, e.g. Auto, Walk, Run"),
|
|
70
|
+
parentId: z.string().optional().describe("GUID of a parent GameObject"),
|
|
71
|
+
}, async (params) => {
|
|
72
|
+
const res = await bridge.send("spawn_citizen", params);
|
|
73
|
+
if (!res.success) {
|
|
74
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
// ── dress_citizen ──────────────────────────────────────────────────
|
|
81
|
+
server.tool("dress_citizen", "Dress a spawned Citizen (or any GameObject with a SkinnedModelRenderer) by applying .clothing resources. Pass an array of clothing resource paths; they're loaded, added to a ClothingContainer, and applied to the body. Returns which paths applied vs were not found.", {
|
|
82
|
+
id: z.string().describe("GUID of the Citizen GameObject"),
|
|
83
|
+
clothing: z
|
|
84
|
+
.array(z.string())
|
|
85
|
+
.describe("Clothing resource paths, e.g. ['models/citizen_clothes/jacket/jacket.clothing']"),
|
|
86
|
+
tint: z
|
|
87
|
+
.number()
|
|
88
|
+
.min(0)
|
|
89
|
+
.max(1)
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("ClothingContainer tint position 0-1 (skin/clothing colour variation)"),
|
|
92
|
+
}, async (params) => {
|
|
93
|
+
const res = await bridge.send("dress_citizen", params);
|
|
94
|
+
if (!res.success) {
|
|
95
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
// ── set_bodygroup ──────────────────────────────────────────────────
|
|
102
|
+
server.tool("set_bodygroup", "Show/hide a bodygroup on a SkinnedModelRenderer (e.g. hide hands when holding a tool, swap head variants). Provide value (int index) or choice (string name).", {
|
|
103
|
+
id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
|
|
104
|
+
name: z.string().describe("Bodygroup name"),
|
|
105
|
+
value: z.number().int().optional().describe("Bodygroup choice index"),
|
|
106
|
+
choice: z.string().optional().describe("Bodygroup choice by name (alternative to value)"),
|
|
107
|
+
}, async (params) => {
|
|
108
|
+
const res = await bridge.send("set_bodygroup", params);
|
|
109
|
+
if (!res.success) {
|
|
110
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
// ── pose_citizen ───────────────────────────────────────────────────
|
|
117
|
+
server.tool("pose_citizen", "Pose a Citizen by setting CitizenAnimationHelper params (enables PlayAnimationsInEditorScene so the pose shows in-editor). Set holdType (None/Pistol/Rifle/Shotgun/HoldItem/Punch/Swing), moveStyle (Auto/Walk/Run), specialMove, sitting (bool), and/or duckLevel (0-1).", {
|
|
118
|
+
id: z.string().describe("GUID of the Citizen GameObject (must have a CitizenAnimationHelper)"),
|
|
119
|
+
holdType: z.string().optional().describe("Hold pose, e.g. None, Pistol, Rifle, Shotgun, HoldItem"),
|
|
120
|
+
moveStyle: z.string().optional().describe("Movement style, e.g. Auto, Walk, Run"),
|
|
121
|
+
specialMove: z.string().optional().describe("Special move style, e.g. None, LedgeGrab, Roll"),
|
|
122
|
+
sitting: z.boolean().optional().describe("Sit the character (IsSitting)"),
|
|
123
|
+
duckLevel: z.number().min(0).max(1).optional().describe("Crouch amount, 0-1"),
|
|
124
|
+
}, async (params) => {
|
|
125
|
+
const res = await bridge.send("pose_citizen", params);
|
|
126
|
+
if (!res.success) {
|
|
127
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
// ── equip_model ────────────────────────────────────────────────────
|
|
134
|
+
server.tool("equip_model", "Attach a prop model (weapon, hat, tool) to a Citizen's bone or attachment point — the prop is parented so it follows that point. Tries the point as an attachment (hand_R, hand_L, eyes, hat) then as a bone. Great for arming NPCs or adding accessories.", {
|
|
135
|
+
id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
|
|
136
|
+
model: z.string().describe("Prop model path, e.g. 'models/dev/box.vmdl'"),
|
|
137
|
+
point: z
|
|
138
|
+
.string()
|
|
139
|
+
.optional()
|
|
140
|
+
.describe("Attachment or bone name (default 'hand_R'; try hand_L, eyes, hat, head)"),
|
|
141
|
+
offset: Vector3Schema.optional().describe("Local position offset from the point"),
|
|
142
|
+
rotation: RotationSchema.optional().describe("Local rotation offset"),
|
|
143
|
+
tint: ColorSchema.optional().describe("Prop tint colour"),
|
|
144
|
+
name: z.string().optional().describe("Name for the prop GameObject"),
|
|
145
|
+
}, async (params) => {
|
|
146
|
+
const res = await bridge.send("equip_model", params);
|
|
147
|
+
if (!res.success) {
|
|
148
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
152
|
+
};
|
|
153
|
+
});
|
|
154
|
+
// ── set_look_at ────────────────────────────────────────────────────
|
|
155
|
+
server.tool("set_look_at", "Aim a Citizen's gaze. Pass target {x,y,z} (spawns a LookTarget) or targetId (existing GameObject) and the head/eyes track it. Pass enabled:false to turn gaze tracking off. Tune eyesWeight/headWeight/bodyWeight (0-1).", {
|
|
156
|
+
id: z.string().describe("GUID of the Citizen (must have a CitizenAnimationHelper)"),
|
|
157
|
+
target: Vector3Schema.optional().describe("World point to look at"),
|
|
158
|
+
targetId: z
|
|
159
|
+
.string()
|
|
160
|
+
.optional()
|
|
161
|
+
.describe("GUID of a GameObject to look at (overrides target)"),
|
|
162
|
+
enabled: z.boolean().optional().describe("false to disable gaze tracking"),
|
|
163
|
+
eyesWeight: z.number().min(0).max(1).optional().describe("Eye look weight 0-1"),
|
|
164
|
+
headWeight: z.number().min(0).max(1).optional().describe("Head turn weight 0-1"),
|
|
165
|
+
bodyWeight: z.number().min(0).max(1).optional().describe("Body turn weight 0-1"),
|
|
166
|
+
}, async (params) => {
|
|
167
|
+
const res = await bridge.send("set_look_at", params);
|
|
168
|
+
if (!res.success) {
|
|
169
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
// ── add_ragdoll ────────────────────────────────────────────────────
|
|
176
|
+
server.tool("add_ragdoll", "Add ModelPhysics to a skinned model so it becomes a ragdoll (physics-driven bones). NOTE: the ragdoll only flops in PLAY mode — it won't move in the static editor view, so this one is verified structurally, not by screenshot.", {
|
|
177
|
+
id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
|
|
178
|
+
motionEnabled: z
|
|
179
|
+
.boolean()
|
|
180
|
+
.optional()
|
|
181
|
+
.describe("Whether physics bodies start with motion enabled"),
|
|
182
|
+
}, async (params) => {
|
|
183
|
+
const res = await bridge.send("add_ragdoll", params);
|
|
184
|
+
if (!res.success) {
|
|
185
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
// ── set_expression ─────────────────────────────────────────────────
|
|
192
|
+
server.tool("set_expression", "Set a facial morph (blendshape) on a skinned model — e.g. smile, frown, blink. Call with NO morph to list the model's available morph names (returned as availableMorphs). weight is typically 0-1.", {
|
|
193
|
+
id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
|
|
194
|
+
morph: z
|
|
195
|
+
.string()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("Morph/blendshape name (omit to list available morphs)"),
|
|
198
|
+
weight: z.number().optional().describe("Morph weight, typically 0-1 (default 1)"),
|
|
199
|
+
}, async (params) => {
|
|
200
|
+
const res = await bridge.send("set_expression", params);
|
|
201
|
+
if (!res.success) {
|
|
202
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=characters.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { BridgeClient } from "../transport/bridge-client.js";
|
|
3
|
+
export declare function registerDiagnosticTools(server: McpServer, bridge: BridgeClient): void;
|
|
4
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|