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 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 — 99 working tools for scenes, scripts, GameObjects, components, assets, materials, audio, physics, UI, networking, publishing, world-gen, 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: 99`.
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 (99 working)
58
-
59
- | Category | Tools |
60
- |----------|-------|
61
- | **Project** | get_project_info, list_project_files, read_file, write_file |
62
- | **Scripts** | create_script, edit_script, delete_script, trigger_hotload |
63
- | **Scenes** | list_scenes, load_scene, save_scene, create_scene |
64
- | **GameObjects** | create/delete/duplicate/rename, set_parent/enabled/transform |
65
- | **Components** | get/set_property, get_all_properties, list_available, add_component, set_prefab_ref |
66
- | **Hierarchy** | get_scene_hierarchy (with `maxDepth` + `rootId`), get/select/focus_object |
67
- | **Assets** | search_assets, list_asset_library, install_asset, get_asset_info |
68
- | **Materials** | assign_model, create/assign_material, set_material_property |
69
- | **Audio** | list_sounds, create_sound_event, assign_sound, play_sound_preview |
70
- | **Play Mode** | start/stop_play, is_playing |
71
- | **Runtime** | get/set_runtime_property, take_screenshot |
72
- | **Editor** | undo, redo |
73
- | **Prefabs** | create/instantiate_prefab, list_prefabs, get_prefab_info |
74
- | **Physics** | add_physics, add_collider, add_joint, raycast |
75
- | **UI** | create_razor_ui, add_screen_panel, add_world_panel |
76
- | **Templates** | create_player/npc_controller, create_game_manager, create_trigger_zone |
77
- | **Networking** | network_helper, configure/status, spawn, ownership, sync, RPCs, lobby/event templates |
78
- | **Publishing** | project_config, validate, thumbnail, package_details |
79
- | **World gen** | invoke_button, list_component_buttons, raycast_terrain, build_terrain_mesh |
80
- | **Map edit** | add_terrain_hill/clearing/trail, clear_terrain_features, sculpt_terrain |
81
- | **Caves / Forest** | add_cave_waypoint, clear_cave_path, add_forest_poi/trail, set_forest_seed, clear_forest_pois, paint_forest_density |
82
- | **Placement** | place_along_path |
83
- | **Discovery** | describe_type, search_types, get_method_signature, find_in_project |
84
- | **Status** | get_bridge_status |
85
-
86
- ## Working with Claude effectively
87
-
88
- Two disciplines prevent the iteration-loop trap:
89
-
90
- 1. **After visual changes, call `take_screenshot` and read the PNG.** Claude is a multimodal model — it can see the result. Guessing about visual outcomes from code alone produces long iteration loops.
91
- 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.
92
-
93
- 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.
94
-
95
- ## Requirements
96
-
97
- - **Node.js 18+**
98
- - **s&box** with the bridge addon installed in your project's `Libraries/` folder
99
- - **Claude Code**
100
-
101
- ## Documentation
102
-
103
- - [Main README](https://github.com/LouSputthole/Sbox-Claude/blob/main/README.md) — full project overview
104
- - [INSTALL.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/INSTALL.md) — install + manual fallback
105
- - [TROUBLESHOOTING.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/TROUBLESHOOTING.md) 10 most common failures
106
- - [CHANGELOG.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/CHANGELOG.md) — release history
107
- - [Plugin README](https://github.com/LouSputthole/Sbox-Claude/blob/main/plugins/sbox-claude/README.md)Claude Code plugin docs
108
-
109
- ## License
110
-
111
- **GPL-3.0** see [LICENSE](../LICENSE) for details.
112
-
113
- Copyright (c) 2026 [sboxskins.gg](https://sboxskins.gg)
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 (99 working was 109; 10 unimplementable tools removed in v1.3.0)
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,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ export declare function registerCharacterTools(server: McpServer, bridge: BridgeClient): void;
4
+ //# sourceMappingURL=characters.d.ts.map
@@ -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