sbox-mcp-server 1.19.0 → 2.0.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.
Files changed (48) hide show
  1. package/dist/index.js +119 -82
  2. package/dist/tools/assets.js +5 -5
  3. package/dist/tools/audio.js +12 -19
  4. package/dist/tools/audit.d.ts +8 -0
  5. package/dist/tools/audit.js +65 -0
  6. package/dist/tools/batch.d.ts +8 -0
  7. package/dist/tools/batch.js +77 -0
  8. package/dist/tools/characters.js +6 -6
  9. package/dist/tools/cinematics.d.ts +19 -0
  10. package/dist/tools/cinematics.js +73 -0
  11. package/dist/tools/components.js +2 -2
  12. package/dist/tools/debugdraw.js +4 -4
  13. package/dist/tools/diagnostics.js +7 -5
  14. package/dist/tools/discovery.js +6 -6
  15. package/dist/tools/gameobjects.js +7 -7
  16. package/dist/tools/gameplay.js +4 -4
  17. package/dist/tools/inspection.js +6 -6
  18. package/dist/tools/interactionpack.d.ts +16 -0
  19. package/dist/tools/interactionpack.js +107 -0
  20. package/dist/tools/leveltools.js +5 -5
  21. package/dist/tools/looteconomy.d.ts +18 -0
  22. package/dist/tools/looteconomy.js +107 -0
  23. package/dist/tools/materials.js +4 -4
  24. package/dist/tools/moviemaker.d.ts +17 -0
  25. package/dist/tools/moviemaker.js +98 -0
  26. package/dist/tools/netprimitives.d.ts +18 -0
  27. package/dist/tools/netprimitives.js +110 -0
  28. package/dist/tools/networking.js +22 -22
  29. package/dist/tools/physics.js +4 -4
  30. package/dist/tools/playmode.js +5 -5
  31. package/dist/tools/playtest.js +2 -1
  32. package/dist/tools/prefabs.js +7 -3
  33. package/dist/tools/project.js +2 -2
  34. package/dist/tools/publishing.js +3 -3
  35. package/dist/tools/scenes.js +2 -2
  36. package/dist/tools/scripts.js +3 -3
  37. package/dist/tools/status.js +1 -1
  38. package/dist/tools/templates.js +7 -7
  39. package/dist/tools/ui.js +5 -5
  40. package/dist/tools/uifeedback.d.ts +16 -0
  41. package/dist/tools/uifeedback.js +83 -0
  42. package/dist/tools/vehicles.d.ts +8 -0
  43. package/dist/tools/vehicles.js +98 -0
  44. package/dist/tools/visuals.js +8 -8
  45. package/dist/tools/workflow.d.ts +8 -0
  46. package/dist/tools/workflow.js +107 -0
  47. package/dist/tools/world.js +61 -58
  48. package/package.json +55 -55
package/dist/index.js CHANGED
@@ -53,6 +53,16 @@ import { registerStationTools } from "./tools/stations.js";
53
53
  import { registerDirectorTools } from "./tools/director.js";
54
54
  import { registerSaveSlotsTools } from "./tools/saveslots.js";
55
55
  import { registerGameFeelTools } from "./tools/gamefeel.js";
56
+ import { registerMovieMakerTools } from "./tools/moviemaker.js";
57
+ import { registerNetPrimitivesTools } from "./tools/netprimitives.js";
58
+ import { registerInteractionPackTools } from "./tools/interactionpack.js";
59
+ import { registerLootEconomyTools } from "./tools/looteconomy.js";
60
+ import { registerUiFeedbackTools } from "./tools/uifeedback.js";
61
+ import { registerCinematicsTools } from "./tools/cinematics.js";
62
+ import { registerAuditTools } from "./tools/audit.js";
63
+ import { registerBatchTools } from "./tools/batch.js";
64
+ import { registerWorkflowTools } from "./tools/workflow.js";
65
+ import { registerVehicleTools } from "./tools/vehicles.js";
56
66
  // ── CLI flags ──────────────────────────────────────────────────────
57
67
  const args = process.argv.slice(2);
58
68
  /** Read the package version from package.json, or return "unknown" on failure. */
@@ -72,68 +82,68 @@ if (args.includes("--version") || args.includes("-v")) {
72
82
  process.exit(0);
73
83
  }
74
84
  if (args.includes("--help") || args.includes("-h")) {
75
- console.log(`sbox-mcp ${getVersion()} — MCP Server for s&box game engine
76
-
77
- USAGE
78
- node dist/index.js Start the MCP server (stdio transport)
79
- node dist/index.js --help Show this help
80
- node dist/index.js --version Show version
81
-
82
- ENVIRONMENT VARIABLES
83
- SBOX_BRIDGE_IPC_DIR IPC directory — MUST match the s&box addon's dir.
84
- Default: <os tmpdir>/sbox-bridge-ipc
85
- SBOX_BRIDGE_HOST Legacy/cosmetic — shown in get_bridge_status only
86
- SBOX_BRIDGE_PORT Legacy/cosmetic — shown in get_bridge_status only
87
-
88
- CONNECT TO CLAUDE CODE
89
- claude mcp add sbox -- node /path/to/sbox-mcp-server/dist/index.js
90
-
91
- TOOLS (150 total / 142 s&box-editor handlers +16 in v1.5.0)
92
- Project: get_project_info, list_project_files, read_file, write_file
93
- Scripts: create_script, edit_script, delete_script, trigger_hotload
94
- Scenes: list_scenes, load_scene, save_scene, create_scene
95
- GameObjects: create/delete/duplicate/rename_gameobject, set_parent/enabled/transform
96
- Components: get/set_property, get_all_properties, list_available_components, add_component_with_properties, set_prefab_ref
97
- Hierarchy: get_scene_hierarchy (with maxDepth + rootId), get_selected_objects, select_object, focus_object
98
- Assets: search_assets, list_asset_library, install_asset, get_asset_info
99
- Materials: assign_model, create_material, assign_material, set_material_property
100
- Audio: list_sounds, create_sound_event, assign_sound, play_sound_preview
101
- Play Mode: start_play, stop_play, is_playing
102
- Runtime: get/set_runtime_property, take_screenshot
103
- Editor: undo, redo
104
- Prefabs: create_prefab, instantiate_prefab, list_prefabs, get_prefab_info
105
- Physics: add_physics, add_collider, add_joint, raycast
106
- UI: create_razor_ui, add_screen_panel, add_world_panel
107
- Templates: create_player_controller, create_npc_controller, create_game_manager, create_trigger_zone
108
- Networking: add_network_helper, configure_network, get_network_status, network_spawn, set_ownership
109
- Net Scripts: add_sync_property, add_rpc_method, create_networked_player, create_lobby_manager, create_network_events
110
- Publishing: get_project_config, set_project_config, validate_project, set_project_thumbnail, get_package_details
111
- World Gen: invoke_button, list_component_buttons, raycast_terrain, build_terrain_mesh
112
- Map Edit: add_terrain_hill/clearing/trail, clear_terrain_features, sculpt_terrain
113
- Caves: add_cave_waypoint, clear_cave_path
114
- Forest: add_forest_poi/trail, set_forest_seed, clear_forest_pois, paint_forest_density
115
- Placement: place_along_path
116
- Discovery: describe_type, search_types, get_method_signature, find_in_project
117
- Status: get_bridge_status
118
-
119
- ── New in v1.4.0 ───────────────────────────────────
120
- Visual: add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look
121
- Characters: spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression
122
- Scene: snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance
123
- Environment: scatter_props, randomize_transforms, group_objects
124
- Utilities: find_objects, set_tint, replace_model, set_tags
125
- VFX (exp): spawn_particle, create_particle_effect, add_trail, add_beam
126
-
127
- ── New in v1.5.0 ───────────────────────────────────
128
- Diagnostics: read_log, get_compile_errors (MCP-server-side — work even if the editor crashed)
129
- Camera: screenshot_from (AIM a shot at an object/point — take_screenshot is fixed to the Main Camera), frame_camera
130
- Navigation: bake_navmesh, get_navmesh_path
131
- Spatial: physics_overlap (volume counterpart to raycast)
132
- Reflections: bake_reflections
133
- Particles: spawn_vpcf (compiled .vpcf via LegacyParticleSystem — the supported particle path)
134
- Console/Exec: console_run, execute_csharp (experimental)
135
- Object utils: remove_component, get_tags
136
- Docs search: search_docs, get_doc_page, list_doc_categories (MCP-server-side — official Facepunch/sbox-docs)
85
+ console.log(`sbox-mcp ${getVersion()} — MCP Server for s&box game engine
86
+
87
+ USAGE
88
+ node dist/index.js Start the MCP server (stdio transport)
89
+ node dist/index.js --help Show this help
90
+ node dist/index.js --version Show version
91
+
92
+ ENVIRONMENT VARIABLES
93
+ SBOX_BRIDGE_IPC_DIR IPC directory — MUST match the s&box addon's dir.
94
+ Default: <os tmpdir>/sbox-bridge-ipc
95
+ SBOX_BRIDGE_HOST Legacy/cosmetic — shown in get_bridge_status only
96
+ SBOX_BRIDGE_PORT Legacy/cosmetic — shown in get_bridge_status only
97
+
98
+ CONNECT TO CLAUDE CODE
99
+ claude mcp add sbox -- node /path/to/sbox-mcp-server/dist/index.js
100
+
101
+ TOOLS (245 total / 237 editor handlers · v2: the native editor MCP server at http://127.0.0.1:7269/mcp is the primary path — this stdio server is the full-surface fallback; --lifeline exposes just the editor-down diagnostics)
102
+ Project: get_project_info, list_project_files, read_file, write_file
103
+ Scripts: create_script, edit_script, delete_script, trigger_hotload
104
+ Scenes: list_scenes, load_scene, save_scene, create_scene
105
+ GameObjects: create/delete/duplicate/rename_gameobject, set_parent/enabled/transform
106
+ Components: get/set_property, get_all_properties, list_available_components, add_component_with_properties, set_prefab_ref
107
+ Hierarchy: get_scene_hierarchy (with maxDepth + rootId), get_selected_objects, select_object, focus_object
108
+ Assets: search_assets, list_asset_library, install_asset, get_asset_info
109
+ Materials: assign_model, create_material, assign_material, set_material_property
110
+ Audio: list_sounds, create_sound_event, assign_sound, play_sound_preview
111
+ Play Mode: start_play, stop_play, is_playing
112
+ Runtime: get/set_runtime_property, take_screenshot
113
+ Editor: undo, redo
114
+ Prefabs: create_prefab, instantiate_prefab, list_prefabs, get_prefab_info
115
+ Physics: add_physics, add_collider, add_joint, raycast
116
+ UI: create_razor_ui, add_screen_panel, add_world_panel
117
+ Templates: create_player_controller, create_npc_controller, create_game_manager, create_trigger_zone
118
+ Networking: add_network_helper, configure_network, get_network_status, network_spawn, set_ownership
119
+ Net Scripts: add_sync_property, add_rpc_method, create_networked_player, create_lobby_manager, create_network_events
120
+ Publishing: get_project_config, set_project_config, validate_project, set_project_thumbnail, get_package_details
121
+ World Gen: invoke_button, list_component_buttons, raycast_terrain, build_terrain_mesh
122
+ Map Edit: add_terrain_hill/clearing/trail, clear_terrain_features, sculpt_terrain
123
+ Caves: add_cave_waypoint, clear_cave_path
124
+ Forest: add_forest_poi/trail, set_forest_seed, clear_forest_pois, paint_forest_density
125
+ Placement: place_along_path
126
+ Discovery: describe_type, search_types, get_method_signature, find_in_project
127
+ Status: get_bridge_status
128
+
129
+ ── New in v1.4.0 ───────────────────────────────────
130
+ Visual: add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look
131
+ Characters: spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression
132
+ Scene: snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance
133
+ Environment: scatter_props, randomize_transforms, group_objects
134
+ Utilities: find_objects, set_tint, replace_model, set_tags
135
+ VFX (exp): spawn_particle, create_particle_effect, add_trail, add_beam
136
+
137
+ ── New in v1.5.0 ───────────────────────────────────
138
+ Diagnostics: read_log, get_compile_errors (MCP-server-side — work even if the editor crashed)
139
+ Camera: screenshot_from (AIM a shot at an object/point — take_screenshot is fixed to the Main Camera), frame_camera
140
+ Navigation: bake_navmesh, get_navmesh_path
141
+ Spatial: physics_overlap (volume counterpart to raycast)
142
+ Reflections: bake_reflections
143
+ Particles: spawn_vpcf (compiled .vpcf via LegacyParticleSystem — the supported particle path)
144
+ Console/Exec: console_run, execute_csharp (experimental)
145
+ Object utils: remove_component, get_tags
146
+ Docs search: search_docs, get_doc_page, list_doc_categories (MCP-server-side — official Facepunch/sbox-docs)
137
147
  `);
138
148
  process.exit(0);
139
149
  }
@@ -146,30 +156,47 @@ const server = new McpServer({
146
156
  // server (the way other MCP servers like Supabase / TurboTax do). Use it to
147
157
  // tell Claude how to work effectively with the bridge — the disciplines that
148
158
  // are easy to skip without a reminder.
149
- instructions: `You are working with the s&box Claude Bridge — a file-based IPC bridge into the s&box game engine editor.
150
-
151
- To get good results:
152
-
153
- 1. Always call \`mcp__sbox__get_bridge_status\` first to confirm the bridge addon is connected and s&box is running. If ping responds but other tools time out, the editor side isn't processing requests.
154
-
155
- 2. For visual changes (models, positions, animations, UI panels, lighting), call \`mcp__sbox__take_screenshot\` after the change and READ THE PNG yourself. You're a multimodal model — you can see the result. Guessing about visual outcomes from code alone produces long iteration loops. The screenshot tool saves to <sbox-install>/screenshots/sbox.<timestamp>.png — list the newest file and read it.
156
-
157
- 3. Before writing code that touches an unfamiliar s&box type, call \`mcp__sbox__describe_type\` or \`mcp__sbox__search_types\`. s&box's API changes between SDK versions — reflection is the source of truth, not training data.
158
-
159
- 4. \`get_scene_hierarchy\` honors \`maxDepth\` (default 10) and accepts optional \`rootId\` to traverse from a specific GameObject. Use these to avoid dumping the entire scene into a tool result.
160
-
161
- 5. Scene-mutating tools (create_gameobject, set_property, etc.) refuse during play mode and return a clear error. Stop play before making scene edits.
162
-
163
- 6. First session with the bridge (or when the user asks "how do I start?" / "what can this do?")? Offer to run setup — invoke the \`sbox-setup\` skill: it verifies the connection, detects the user's installed libraries (\`list_libraries\`), recommends a first move, and points to help + feedback.
164
-
165
- If you're running inside Claude Code, install the companion plugin for the full workflow:
166
- /plugin marketplace add LouSputthole/Sbox-Claude
167
- /plugin install sbox-claude
168
-
159
+ instructions: `You are working with the s&box Claude Bridge — a file-based IPC bridge into the s&box game engine editor.
160
+
161
+ To get good results:
162
+
163
+ 1. Always call \`mcp__sbox__get_bridge_status\` first to confirm the bridge addon is connected and s&box is running. If ping responds but other tools time out, the editor side isn't processing requests.
164
+
165
+ 2. For visual changes (models, positions, animations, UI panels, lighting), call \`mcp__sbox__take_screenshot\` after the change and READ THE PNG yourself. You're a multimodal model — you can see the result. Guessing about visual outcomes from code alone produces long iteration loops. The screenshot tool saves to <sbox-install>/screenshots/sbox.<timestamp>.png — list the newest file and read it.
166
+
167
+ 3. Before writing code that touches an unfamiliar s&box type, call \`mcp__sbox__describe_type\` or \`mcp__sbox__search_types\`. s&box's API changes between SDK versions — reflection is the source of truth, not training data.
168
+
169
+ 4. \`get_scene_hierarchy\` honors \`maxDepth\` (default 10) and accepts optional \`rootId\` to traverse from a specific GameObject. Use these to avoid dumping the entire scene into a tool result.
170
+
171
+ 5. Scene-mutating tools (create_gameobject, set_property, etc.) refuse during play mode and return a clear error. Stop play before making scene edits.
172
+
173
+ 6. First session with the bridge (or when the user asks "how do I start?" / "what can this do?")? Offer to run setup — invoke the \`sbox-setup\` skill: it verifies the connection, detects the user's installed libraries (\`list_libraries\`), recommends a first move, and points to help + feedback.
174
+
175
+ If you're running inside Claude Code, install the companion plugin for the full workflow:
176
+ /plugin marketplace add LouSputthole/Sbox-Claude
177
+ /plugin install sbox-claude
178
+
169
179
  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.`,
170
180
  });
171
181
  // Bridge client talks to the s&box editor via file IPC. host/port are cosmetic.
172
182
  const bridge = new BridgeClient(process.env.SBOX_BRIDGE_HOST ?? "127.0.0.1", parseInt(process.env.SBOX_BRIDGE_PORT ?? "29015", 10));
183
+ // ── Lifeline mode (v2 migration) ───────────────────────────────────
184
+ // With the native editor MCP server carrying the full tool surface, this stdio
185
+ // server's long-term job is "editor-down diagnostics": the native server dies
186
+ // with the editor; these tools read the log / docs directly and don't. --lifeline
187
+ // registers ONLY that set by filtering server.tool() — everything else is a no-op.
188
+ const LIFELINE_TOOLS = new Set([
189
+ "read_log", "get_compile_errors", "search_docs", "get_doc_page",
190
+ "list_doc_categories", "run_self_test", "get_bridge_status",
191
+ ]);
192
+ if (args.includes("--lifeline")) {
193
+ const realTool = server.tool.bind(server);
194
+ server.tool = (...toolArgs) => {
195
+ if (LIFELINE_TOOLS.has(toolArgs[0]))
196
+ return realTool(...toolArgs);
197
+ return undefined;
198
+ };
199
+ }
173
200
  // Register all tools
174
201
  registerProjectTools(server, bridge);
175
202
  registerScriptTools(server, bridge);
@@ -209,6 +236,16 @@ registerStationTools(server, bridge);
209
236
  registerDirectorTools(server, bridge);
210
237
  registerSaveSlotsTools(server, bridge);
211
238
  registerGameFeelTools(server, bridge);
239
+ registerMovieMakerTools(server, bridge);
240
+ registerNetPrimitivesTools(server, bridge);
241
+ registerInteractionPackTools(server, bridge);
242
+ registerLootEconomyTools(server, bridge);
243
+ registerUiFeedbackTools(server, bridge);
244
+ registerCinematicsTools(server, bridge);
245
+ registerAuditTools(server, bridge);
246
+ registerBatchTools(server, bridge);
247
+ registerWorkflowTools(server, bridge);
248
+ registerVehicleTools(server, bridge);
212
249
  /** Start the MCP server on stdio and attempt initial Bridge connection. */
213
250
  async function main() {
214
251
  const transport = new StdioServerTransport();
@@ -28,19 +28,19 @@ export function registerAssetTools(server, bridge) {
28
28
  };
29
29
  });
30
30
  // ── list_asset_library ───────────────────────────────────────────
31
- server.tool("list_asset_library", "Browse the s&box community asset library. Search for packages by name, description, or type to find models, maps, and tools to install", {
31
+ server.tool("list_asset_library", "List assets visible to the editor's AssetSystem project assets plus mounted engine/installed-package content (it does NOT query the remote sbox.game library; use install_asset to pull in new packages). Returns { count, assets:[{ name, path, relativePath, assetType }] } pass a returned path to spawn_model, assign_model, or get_asset_info", {
32
32
  query: z
33
33
  .string()
34
34
  .optional()
35
- .describe("Search term for packages"),
35
+ .describe("Search term, case-insensitive substring match against asset NAMES"),
36
36
  type: z
37
37
  .string()
38
38
  .optional()
39
- .describe("Package type filter (e.g. 'model', 'map', 'library')"),
39
+ .describe("Asset type filter, substring-matched against the asset's type (e.g. 'model', 'material', 'sound')"),
40
40
  maxResults: z
41
41
  .number()
42
42
  .optional()
43
- .describe("Maximum results. Defaults to 25"),
43
+ .describe("Maximum results. Defaults to 200"),
44
44
  }, async (params) => {
45
45
  const res = await bridge.send("list_asset_library", params);
46
46
  if (!res.success) {
@@ -51,7 +51,7 @@ export function registerAssetTools(server, bridge) {
51
51
  };
52
52
  });
53
53
  // ── install_asset ────────────────────────────────────────────────
54
- server.tool("install_asset", "Install a community asset package into the project by its ident (e.g. 'facepunch.flatgrass'). Adds it as a project dependency", {
54
+ server.tool("install_asset", "Install a community asset package into the project by its ident (e.g. 'facepunch.flatgrass'). Adds it as a project dependency. Returns { installed, ident, name, path, relativePath, restartRecommended, note } — pass the returned path to spawn_model / assign_model. CAUTION: if the package is a code LIBRARY (adds a PackageReference), trigger_hotload will NOT surface its types — call restart_editor so the new package compiles into the project", {
55
55
  ident: z
56
56
  .string()
57
57
  .describe("Package identifier (e.g. 'facepunch.flatgrass', 'author.package_name')"),
@@ -5,15 +5,15 @@ import { z } from "zod";
5
5
  */
6
6
  export function registerAudioTools(server, bridge) {
7
7
  // ── list_sounds ──────────────────────────────────────────────────
8
- server.tool("list_sounds", "List available sound assets in the project and installed packages. Filter by name", {
8
+ server.tool("list_sounds", "List the project's .sound event files (recursive scan of the project root for *.sound). Returns { count, sounds } — project-relative paths ready to pass to assign_sound, play_sound_preview, or add_lipsync. NOTE: the current handler returns every match (filter/maxResults are not applied) and only covers .sound files in the project tree — use search_assets type='sound' for other sound assets", {
9
9
  filter: z
10
10
  .string()
11
11
  .optional()
12
- .describe("Search filter for sound name or path"),
12
+ .describe("Search filter for sound name or path (currently not applied by the handler — all .sound files are returned)"),
13
13
  maxResults: z
14
14
  .number()
15
15
  .optional()
16
- .describe("Maximum results. Defaults to 50"),
16
+ .describe("Maximum results. Defaults to 50 (currently not applied by the handler)"),
17
17
  }, async (params) => {
18
18
  const res = await bridge.send("list_sounds", params);
19
19
  if (!res.success) {
@@ -24,13 +24,14 @@ export function registerAudioTools(server, bridge) {
24
24
  };
25
25
  });
26
26
  // ── create_sound_event ───────────────────────────────────────────
27
- server.tool("create_sound_event", "Create a new sound event file (.sound) with volume, pitch, distance falloff, and looping settings", {
27
+ server.tool("create_sound_event", "Create a .sound event file wired to a source .vsnd. Returns { created, path, soundReferenced, note } (path is project-relative); errors if the file already exists. Preview the result with play_sound_preview or attach it to an object with assign_sound. Note: .sound events have no loop flag — looping lives on the SoundPointComponent that plays the event", {
28
28
  path: z
29
29
  .string()
30
- .describe("Path for the sound event file (e.g. 'sounds/footstep.sound')"),
30
+ .describe("Project-relative path for the sound event file (e.g. 'sounds/footstep.sound'; '.sound' appended if missing)"),
31
31
  sound: z
32
32
  .string()
33
- .describe("Path to the source sound asset (.vsnd)"),
33
+ .optional()
34
+ .describe("Path to the source sound asset (.vsnd) the event plays. Omit to create an empty event and wire it later"),
34
35
  volume: z
35
36
  .number()
36
37
  .optional()
@@ -39,18 +40,10 @@ export function registerAudioTools(server, bridge) {
39
40
  .number()
40
41
  .optional()
41
42
  .describe("Pitch multiplier. Defaults to 1.0"),
42
- minDistance: z
43
- .number()
44
- .optional()
45
- .describe("Minimum distance before falloff starts (units). Defaults to 100"),
46
43
  maxDistance: z
47
44
  .number()
48
45
  .optional()
49
- .describe("Maximum audible distance (units). Defaults to 2000"),
50
- loop: z
51
- .boolean()
52
- .optional()
53
- .describe("Whether the sound should loop. Defaults to false"),
46
+ .describe("Maximum audible distance in units (sets Distance + enables DistanceAttenuation). Omit for the engine default"),
54
47
  }, async (params) => {
55
48
  const res = await bridge.send("create_sound_event", params);
56
49
  if (!res.success) {
@@ -61,7 +54,7 @@ export function registerAudioTools(server, bridge) {
61
54
  };
62
55
  });
63
56
  // ── assign_sound ─────────────────────────────────────────────────
64
- server.tool("assign_sound", "Attach a sound event to a GameObject via SoundPointComponent. Creates the component if needed", {
57
+ server.tool("assign_sound", "Attach a sound event to a GameObject via SoundPointComponent. Creates the component if needed. Returns { assigned, id, sound, soundLoaded, playOnStart } — soundLoaded:false means the .sound path did not resolve (the component is still added with no event; verify the path with list_sounds)", {
65
58
  id: z.string().describe("GUID of the GameObject"),
66
59
  sound: z
67
60
  .string()
@@ -69,7 +62,7 @@ export function registerAudioTools(server, bridge) {
69
62
  playOnStart: z
70
63
  .boolean()
71
64
  .optional()
72
- .describe("Whether the sound plays automatically when the game starts"),
65
+ .describe("If true, the handler calls StartSound() immediately, so the sound starts playing right away (audible in the editor)"),
73
66
  }, async (params) => {
74
67
  const res = await bridge.send("assign_sound", params);
75
68
  if (!res.success) {
@@ -80,14 +73,14 @@ export function registerAudioTools(server, bridge) {
80
73
  };
81
74
  });
82
75
  // ── play_sound_preview ───────────────────────────────────────────
83
- server.tool("play_sound_preview", "Play a sound in the editor for testing without entering play mode", {
76
+ server.tool("play_sound_preview", "Play a sound in the editor for testing without entering play mode. Returns { playing, sound, volume }. Fire-and-forget via Sound.Play — there is no stop control, and the volume param is echoed back but not currently applied to playback", {
84
77
  sound: z
85
78
  .string()
86
79
  .describe("Sound event or asset path to preview"),
87
80
  volume: z
88
81
  .number()
89
82
  .optional()
90
- .describe("Preview volume (0-1). Defaults to 1.0"),
83
+ .describe("Preview volume (0-1). Defaults to 1.0 (echoed in the response but not currently applied to playback)"),
91
84
  }, async (params) => {
92
85
  const res = await bridge.send("play_sound_preview", params);
93
86
  if (!res.success) {
@@ -0,0 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * Project audit & batch operation tools (v2 relaunch wave 1, Batch 51):
5
+ * find_broken_references, batch_set_property, describe_project.
6
+ */
7
+ export declare function registerAuditTools(server: McpServer, bridge: BridgeClient): void;
8
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Project audit & batch operation tools (v2 relaunch wave 1, Batch 51):
4
+ * find_broken_references, batch_set_property, describe_project.
5
+ */
6
+ export function registerAuditTools(server, bridge) {
7
+ // ── find_broken_references ───────────────────────────────────────
8
+ server.tool("find_broken_references", "Scan the project for broken references, two layers in one call: (1) every GameObject in the open scene — renderers with no Model (missing_model), component properties pointing at DESTROYED GameObjects/Components (dead_gameobject_ref / dead_component_ref), null component entries whose type no longer exists (missing_component); (2) every .scene/.prefab FILE — prefab references to deleted/renamed files (missing_prefab_file). Returns { total, showing, truncated, objectsScanned, filesScanned, issues } — each issue has { id, name, component, kind, detail } (file-level issues carry the file path in name). Fix missing models with assign_model, dead refs with set_property/set_component_reference, missing prefab files by fixing the path or recreating via create_prefab. Read-only; safe any time. Results cap at `limit` (default 100, max 500)", {
9
+ limit: z
10
+ .number()
11
+ .int()
12
+ .optional()
13
+ .describe("Max issues to return (default 100, max 500). total still counts everything"),
14
+ scanFiles: z
15
+ .boolean()
16
+ .optional()
17
+ .describe("Include the .scene/.prefab file scan for missing prefab references. Default true"),
18
+ }, async (params) => {
19
+ const res = await bridge.send("find_broken_references", params);
20
+ if (!res.success) {
21
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
22
+ }
23
+ return {
24
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
25
+ };
26
+ });
27
+ // ── batch_set_property ───────────────────────────────────────────
28
+ server.tool("batch_set_property", "Set ONE component property to the same value across MANY GameObjects in a single call (e.g. Tint on 40 props, Enabled on every light). Pass dryRun:true first to validate — it reports each object's current value and what would change WITHOUT applying anything. Returns { total, succeeded, failed, dryRun, results } with per-object ok/error and the previous value on success. Get ids from find_objects or get_selected_objects. Scene-mutating: refused during play mode. Value coercion matches set_property (numbers, bools, enums, 'x,y,z' vectors, colors, asset paths)", {
29
+ ids: z
30
+ .array(z.string())
31
+ .describe("GUIDs of the GameObjects to modify (from find_objects, get_scene_hierarchy, or get_selected_objects)"),
32
+ component: z
33
+ .string()
34
+ .describe("Component type name present on each object, e.g. 'ModelRenderer', 'PointLight'"),
35
+ property: z
36
+ .string()
37
+ .describe("Property name to set, e.g. 'Tint', 'Enabled', 'LightColor' (see get_all_properties)"),
38
+ value: z
39
+ .any()
40
+ .describe("New value — number, bool, string, enum name, 'x,y,z' vector, color, or asset path"),
41
+ dryRun: z
42
+ .boolean()
43
+ .optional()
44
+ .describe("true = validate and report current values without changing anything. Default false"),
45
+ }, async (params) => {
46
+ const res = await bridge.send("batch_set_property", params);
47
+ if (!res.success) {
48
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
49
+ }
50
+ return {
51
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
52
+ };
53
+ });
54
+ // ── describe_project ─────────────────────────────────────────────
55
+ server.tool("describe_project", "One-call project orientation: identity (name/ident/org/type), the open scene with object count, scene and prefab file lists (capped at 50 each, Libraries/.sbox excluded), code footprint (.cs/.razor counts), custom Component types (up to 100, engine types excluded), and installed libraries. Returns a structured summary — orient here first, then get_scene_hierarchy for the scene, describe_type for components, find_broken_references for project health. Read-only", {}, async (params) => {
56
+ const res = await bridge.send("describe_project", params);
57
+ if (!res.success) {
58
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
59
+ }
60
+ return {
61
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
62
+ };
63
+ });
64
+ }
65
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1,8 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * Batch operation tools (Batch 52): batch_delete, batch_add_component,
5
+ * batch_reparent. All follow the dryRun convention from batch_set_property.
6
+ */
7
+ export declare function registerBatchTools(server: McpServer, bridge: BridgeClient): void;
8
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Batch operation tools (Batch 52): batch_delete, batch_add_component,
4
+ * batch_reparent. All follow the dryRun convention from batch_set_property.
5
+ */
6
+ export function registerBatchTools(server, bridge) {
7
+ // ── batch_delete ─────────────────────────────────────────────────
8
+ server.tool("batch_delete", "Delete MANY GameObjects (and their entire subtrees) in one call. ALWAYS pass dryRun:true first — it reports each object's name and child count WITHOUT deleting, so you can confirm the target list. Returns { total, succeeded, failed, dryRun, results } with per-object ok/error. Destructive and NOT undoable — get ids from find_objects or get_selected_objects and verify the dry-run before applying. Scene-mutating: refused during play mode", {
9
+ ids: z
10
+ .array(z.string())
11
+ .describe("GUIDs of the GameObjects to delete (subtrees included)"),
12
+ dryRun: z
13
+ .boolean()
14
+ .optional()
15
+ .describe("true = report names/child counts without deleting anything. Default false — but run a dry pass first"),
16
+ }, async (params) => {
17
+ const res = await bridge.send("batch_delete", params);
18
+ if (!res.success) {
19
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
20
+ }
21
+ return {
22
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
23
+ };
24
+ });
25
+ // ── batch_add_component ──────────────────────────────────────────
26
+ server.tool("batch_add_component", "Add one component type to MANY GameObjects in one call (e.g. BoxCollider on every crate). Skips objects that already have the component unless skipExisting:false. dryRun:true validates the type and reports per-object alreadyHas WITHOUT adding. Returns { total, succeeded, failed, skipped, dryRun, results }. Follow with batch_set_property to configure the new components. Scene-mutating: refused during play mode", {
27
+ ids: z
28
+ .array(z.string())
29
+ .describe("GUIDs of the target GameObjects (from find_objects or get_selected_objects)"),
30
+ component: z
31
+ .string()
32
+ .describe("Component type name to add, e.g. 'BoxCollider', 'PointLight' (search with list_available_components)"),
33
+ skipExisting: z
34
+ .boolean()
35
+ .optional()
36
+ .describe("Skip objects that already have this component type. Default true"),
37
+ dryRun: z
38
+ .boolean()
39
+ .optional()
40
+ .describe("true = validate and report without adding anything. Default false"),
41
+ }, async (params) => {
42
+ const res = await bridge.send("batch_add_component", params);
43
+ if (!res.success) {
44
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
45
+ }
46
+ return {
47
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
48
+ };
49
+ });
50
+ // ── batch_reparent ───────────────────────────────────────────────
51
+ server.tool("batch_reparent", "Move MANY GameObjects under a new parent (or to the scene root) in one call — organize scattered props into a folder object, or regroup a level section. dryRun:true reports each object's current parent and destination WITHOUT moving. Returns { total, succeeded, failed, dryRun, keepWorldPosition, results }. Errors if parent is among ids (no cycles). Scene-mutating: refused during play mode", {
52
+ ids: z
53
+ .array(z.string())
54
+ .describe("GUIDs of the GameObjects to move"),
55
+ parent: z
56
+ .string()
57
+ .optional()
58
+ .describe("GUID of the new parent GameObject. Omit (or empty) to move to the scene root"),
59
+ keepWorldPosition: z
60
+ .boolean()
61
+ .optional()
62
+ .describe("Preserve each object's world position while reparenting. Default true"),
63
+ dryRun: z
64
+ .boolean()
65
+ .optional()
66
+ .describe("true = report current parents and the destination without moving anything. Default false"),
67
+ }, async (params) => {
68
+ const res = await bridge.send("batch_reparent", params);
69
+ if (!res.success) {
70
+ return { content: [{ type: "text", text: `Error: ${res.error}` }] };
71
+ }
72
+ return {
73
+ content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }],
74
+ };
75
+ });
76
+ }
77
+ //# sourceMappingURL=batch.js.map
@@ -56,7 +56,7 @@ export function registerCharacterTools(server, bridge) {
56
56
  };
57
57
  });
58
58
  // ── spawn_citizen ──────────────────────────────────────────────────
59
- 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.", {
59
+ 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). Returns { created, hasAnimator, gameObject:{ id, name, position, components, ... } } — pass gameObject.id to the follow-ups: dress_citizen, pose_citizen, set_expression, equip_model.", {
60
60
  name: z.string().optional().describe("GameObject name (default 'Citizen')"),
61
61
  model: z
62
62
  .string()
@@ -110,7 +110,7 @@ export function registerCharacterTools(server, bridge) {
110
110
  };
111
111
  });
112
112
  // ── set_bodygroup ──────────────────────────────────────────────────
113
- 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).", {
113
+ 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). Returns { set, bodygroup } on success (errors if the object has no SkinnedModelRenderer or neither value nor choice is given); take_screenshot to verify the visual change.", {
114
114
  id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
115
115
  name: z.string().describe("Bodygroup name"),
116
116
  value: z.number().int().optional().describe("Bodygroup choice index"),
@@ -125,7 +125,7 @@ export function registerCharacterTools(server, bridge) {
125
125
  };
126
126
  });
127
127
  // ── pose_citizen ───────────────────────────────────────────────────
128
- 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).", {
128
+ 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). Returns { posed, changed:[...], gameObject } — changed lists which helper params were applied; take_screenshot to verify the pose.", {
129
129
  id: z.string().describe("GUID of the Citizen GameObject (must have a CitizenAnimationHelper)"),
130
130
  holdType: z.string().optional().describe("Hold pose, e.g. None, Pistol, Rifle, Shotgun, HoldItem"),
131
131
  moveStyle: z.string().optional().describe("Movement style, e.g. Auto, Walk, Run"),
@@ -184,7 +184,7 @@ export function registerCharacterTools(server, bridge) {
184
184
  };
185
185
  });
186
186
  // ── add_ragdoll ────────────────────────────────────────────────────
187
- 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.", {
187
+ 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. Returns { ragdoll, note, gameObject } — check gameObject.components for ModelPhysics, then verify at runtime with start_play + capture_view.", {
188
188
  id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
189
189
  motionEnabled: z
190
190
  .boolean()
@@ -200,7 +200,7 @@ export function registerCharacterTools(server, bridge) {
200
200
  };
201
201
  });
202
202
  // ── set_expression ─────────────────────────────────────────────────
203
- 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.", {
203
+ 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. Returns { set, morph, weight, availableMorphs } — availableMorphs is included in every response, so a wrong name can be corrected without an extra listing call.", {
204
204
  id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
205
205
  morph: z
206
206
  .string()
@@ -244,7 +244,7 @@ export function registerCharacterTools(server, bridge) {
244
244
  };
245
245
  });
246
246
  // ── set_animgraph_param ────────────────────────────────────────────
247
- server.tool("set_animgraph_param", "Set an AnimationGraph parameter on a GameObject's SkinnedModelRenderer (calls Set). This drives Citizen/animgraph motion — e.g. 'move_x'/'move_y' (float), 'b_grounded'/'b_ducked' (bool), or a Vector3. Pose previews in-editor when PlayAnimationsInEditorScene is on; screenshot to verify. Param names are defined by the model's animation graph.", {
247
+ server.tool("set_animgraph_param", "Set an AnimationGraph parameter on a GameObject's SkinnedModelRenderer (calls Set). This drives Citizen/animgraph motion — e.g. 'move_x'/'move_y' (float), 'b_grounded'/'b_ducked' (bool), or a Vector3. Pose previews in-editor when PlayAnimationsInEditorScene is on; screenshot to verify. Param names are defined by the model's animation graph — use list_animations to check whether the model is animgraph-driven. Returns { set, param, kind, note } — kind is the type actually applied (float/int/bool/vector).", {
248
248
  id: z.string().describe("GUID of the GameObject with a SkinnedModelRenderer"),
249
249
  param: z.string().describe("Animgraph parameter name, e.g. 'move_x', 'b_grounded'"),
250
250
  value: z
@@ -0,0 +1,19 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { BridgeClient } from "../transport/bridge-client.js";
3
+ /**
4
+ * Cinematics & Dialogue pack — two engine-proof, no-asset scaffolds (v1.20.0):
5
+ *
6
+ * - create_cutscene_director hand-authored camera-shot cutscene player
7
+ * - create_dialogue_system typewriter NPC/story dialogue (Component + Razor HUD)
8
+ *
9
+ * These are the HAND-AUTHORED cinematic path (zero assets, full C# control). The
10
+ * keyframed/timeline path is the MovieMaker family (add_movie_player / play_movie
11
+ * / stop_movie / list_movies), which wires a Sandbox.MovieMaker MoviePlayer to a
12
+ * .movie clip authored in the editor's Movie Maker dock.
13
+ *
14
+ * All generate self-contained, sandbox-safe, LOCAL/visual-only code; the Razor
15
+ * output is razor_lint-safe by construction. File/scene-mutating, so refused
16
+ * during play mode by the bridge dispatch.
17
+ */
18
+ export declare function registerCinematicsTools(server: McpServer, bridge: BridgeClient): void;
19
+ //# sourceMappingURL=cinematics.d.ts.map