sbox-mcp-server 1.3.1 → 1.4.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,119 @@
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 — 131 working tools for scenes, scripts, GameObjects, components, assets, materials, audio, physics, UI, networking, publishing, world-gen, lighting & atmosphere, characters, scene layout, 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 (131 — v1.4.0)
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
+ | **Visual & atmosphere** *(v1.4.0)* | add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look |
86
+ | **Characters** *(v1.4.0)* | spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression |
87
+ | **Scene & level** *(v1.4.0)* | snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance |
88
+ | **Environment** *(v1.4.0)* | scatter_props, randomize_transforms, group_objects |
89
+ | **Object utilities** *(v1.4.0)* | find_objects, set_tint, replace_model, set_tags |
90
+ | **VFX** *(v1.4.0, experimental)* | spawn_particle, create_particle_effect, add_trail, add_beam compile but runtime rendering unverified through the bridge; use a legacy `.vpcf` for visible particles |
91
+
92
+ ## Working with Claude effectively
93
+
94
+ Two disciplines prevent the iteration-loop trap:
95
+
96
+ 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.
97
+ 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.
98
+
99
+ 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.
100
+
101
+ ## Requirements
102
+
103
+ - **Node.js 18+**
104
+ - **s&box** with the bridge addon installed in your project's `Libraries/` folder
105
+ - **Claude Code**
106
+
107
+ ## Documentation
108
+
109
+ - [Main README](https://github.com/LouSputthole/Sbox-Claude/blob/main/README.md) — full project overview
110
+ - [INSTALL.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/INSTALL.md) — install + manual fallback
111
+ - [TROUBLESHOOTING.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/TROUBLESHOOTING.md) 10 most common failures
112
+ - [CHANGELOG.md](https://github.com/LouSputthole/Sbox-Claude/blob/main/CHANGELOG.md) — release history
113
+ - [Plugin README](https://github.com/LouSputthole/Sbox-Claude/blob/main/plugins/sbox-claude/README.md) — Claude Code plugin docs
114
+
115
+ ## License
116
+
117
+ **GPL-3.0** — see [LICENSE](../LICENSE) for details.
118
+
119
+ Copyright (c) 2026 [sboxskins.gg](https://sboxskins.gg)
package/dist/index.d.ts CHANGED
@@ -3,11 +3,11 @@
3
3
  * Entry point for the sbox-mcp MCP server.
4
4
  *
5
5
  * Creates an MCP server (stdio transport), connects to the s&box Bridge Addon
6
- * via WebSocket, and registers all tool handlers. Each tool domain (project,
6
+ * via file-based IPC (a shared temp dir), and registers all tool handlers. Each tool domain (project,
7
7
  * scripts, console, scenes, etc.) has its own register function in src/tools/.
8
8
  *
9
9
  * CLI flags: --version / -v, --help / -h
10
- * Environment: SBOX_BRIDGE_HOST, SBOX_BRIDGE_PORT
10
+ * Environment: SBOX_BRIDGE_IPC_DIR (the real knob); SBOX_BRIDGE_HOST / SBOX_BRIDGE_PORT (legacy, cosmetic)
11
11
  */
12
12
  export {};
13
13
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -3,11 +3,11 @@
3
3
  * Entry point for the sbox-mcp MCP server.
4
4
  *
5
5
  * Creates an MCP server (stdio transport), connects to the s&box Bridge Addon
6
- * via WebSocket, and registers all tool handlers. Each tool domain (project,
6
+ * via file-based IPC (a shared temp dir), and registers all tool handlers. Each tool domain (project,
7
7
  * scripts, console, scenes, etc.) has its own register function in src/tools/.
8
8
  *
9
9
  * CLI flags: --version / -v, --help / -h
10
- * Environment: SBOX_BRIDGE_HOST, SBOX_BRIDGE_PORT
10
+ * Environment: SBOX_BRIDGE_IPC_DIR (the real knob); SBOX_BRIDGE_HOST / SBOX_BRIDGE_PORT (legacy, cosmetic)
11
11
  */
12
12
  import { readFileSync } from "fs";
13
13
  import { fileURLToPath } from "url";
@@ -33,6 +33,10 @@ 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";
36
40
  // ── CLI flags ──────────────────────────────────────────────────────
37
41
  const args = process.argv.slice(2);
38
42
  /** Read the package version from package.json, or return "unknown" on failure. */
@@ -60,13 +64,15 @@ USAGE
60
64
  node dist/index.js --version Show version
61
65
 
62
66
  ENVIRONMENT VARIABLES
63
- SBOX_BRIDGE_HOST Bridge WebSocket host (default: 127.0.0.1)
64
- SBOX_BRIDGE_PORT Bridge WebSocket port (default: 29015)
67
+ SBOX_BRIDGE_IPC_DIR IPC directory MUST match the s&box addon's dir.
68
+ Default: <os tmpdir>/sbox-bridge-ipc
69
+ SBOX_BRIDGE_HOST Legacy/cosmetic — shown in get_bridge_status only
70
+ SBOX_BRIDGE_PORT Legacy/cosmetic — shown in get_bridge_status only
65
71
 
66
72
  CONNECT TO CLAUDE CODE
67
73
  claude mcp add sbox -- node /path/to/sbox-mcp-server/dist/index.js
68
74
 
69
- TOOLS (99 working was 109; 10 unimplementable tools removed in v1.3.0)
75
+ TOOLS (131+32 in v1.4.0: visual, characters, scene, environment, utilities)
70
76
  Project: get_project_info, list_project_files, read_file, write_file
71
77
  Scripts: create_script, edit_script, delete_script, trigger_hotload
72
78
  Scenes: list_scenes, load_scene, save_scene, create_scene
@@ -93,6 +99,14 @@ TOOLS (99 working — was 109; 10 unimplementable tools removed in v1.3.0)
93
99
  Placement: place_along_path
94
100
  Discovery: describe_type, search_types, get_method_signature, find_in_project
95
101
  Status: get_bridge_status
102
+
103
+ ── New in v1.4.0 ───────────────────────────────────
104
+ Visual: add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look
105
+ Characters: spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression
106
+ Scene: snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance
107
+ Environment: scatter_props, randomize_transforms, group_objects
108
+ Utilities: find_objects, set_tint, replace_model, set_tags
109
+ VFX (exp): spawn_particle, create_particle_effect, add_trail, add_beam
96
110
  `);
97
111
  process.exit(0);
98
112
  }
@@ -125,7 +139,7 @@ If you're running inside Claude Code, install the companion plugin for the full
125
139
 
126
140
  The plugin ships an \`sbox-build-feature\` skill that codifies the workflow above plus a list of common s&box gotchas (MathF not available in sandbox, Cloud assets ephemeral, head bone case-sensitive, CitizenAnimationHelper.IkRightHand works at runtime, etc.). Read its SKILL.md before starting non-trivial features.`,
127
141
  });
128
- // Bridge client connects to s&box editor via WebSocket
142
+ // Bridge client talks to the s&box editor via file IPC. host/port are cosmetic.
129
143
  const bridge = new BridgeClient(process.env.SBOX_BRIDGE_HOST ?? "127.0.0.1", parseInt(process.env.SBOX_BRIDGE_PORT ?? "29015", 10));
130
144
  // Register all tools
131
145
  registerProjectTools(server, bridge);
@@ -146,6 +160,10 @@ registerNetworkingTools(server, bridge);
146
160
  registerPublishingTools(server, bridge);
147
161
  registerWorldTools(server, bridge);
148
162
  registerDiscoveryTools(server, bridge);
163
+ registerVisualTools(server, bridge);
164
+ registerCharacterTools(server, bridge);
165
+ registerLevelTools(server, bridge);
166
+ registerObjectTools(server, bridge);
149
167
  /** Start the MCP server on stdio and attempt initial Bridge connection. */
150
168
  async function main() {
151
169
  const transport = new StdioServerTransport();
@@ -159,6 +177,7 @@ async function main() {
159
177
  console.error(" ║ https://sboxskins.gg ║");
160
178
  console.error(" ╚═══════════════════════════════════════════════════╝");
161
179
  console.error("");
180
+ console.error(`[sbox-mcp] IPC directory: ${bridge.getIpcDir()}`);
162
181
  // Attempt initial connection to s&box (non-fatal if it fails)
163
182
  try {
164
183
  await bridge.connect();
@@ -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 registerLevelTools(server: McpServer, bridge: BridgeClient): void;
4
+ //# sourceMappingURL=leveltools.d.ts.map