sbox-mcp-server 1.20.0 → 2.1.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/dist/index.js +125 -82
- package/dist/tools/aisystems.d.ts +4 -0
- package/dist/tools/aisystems.js +236 -0
- package/dist/tools/assets.js +5 -5
- package/dist/tools/audio.js +12 -19
- package/dist/tools/audit.d.ts +8 -0
- package/dist/tools/audit.js +65 -0
- package/dist/tools/batch.d.ts +8 -0
- package/dist/tools/batch.js +77 -0
- package/dist/tools/characters.js +6 -6
- package/dist/tools/cinematicrecording.d.ts +31 -0
- package/dist/tools/cinematicrecording.js +107 -0
- package/dist/tools/cinematics.js +2 -2
- package/dist/tools/components.js +2 -2
- package/dist/tools/debugdraw.js +4 -4
- package/dist/tools/diagnostics.js +7 -5
- package/dist/tools/dialoguefx.d.ts +4 -0
- package/dist/tools/dialoguefx.js +144 -0
- package/dist/tools/discovery.js +6 -6
- package/dist/tools/economysave.d.ts +27 -0
- package/dist/tools/economysave.js +231 -0
- package/dist/tools/gameobjects.js +7 -7
- package/dist/tools/gameplay.js +4 -4
- package/dist/tools/gameplayrecorder.d.ts +18 -0
- package/dist/tools/gameplayrecorder.js +73 -0
- package/dist/tools/inspection.js +6 -6
- package/dist/tools/leveltools.js +5 -5
- package/dist/tools/materials.js +4 -4
- package/dist/tools/movieauthoring.d.ts +31 -0
- package/dist/tools/movieauthoring.js +84 -0
- package/dist/tools/moviemaker.js +1 -1
- package/dist/tools/multiplayertest.d.ts +23 -0
- package/dist/tools/multiplayertest.js +62 -0
- package/dist/tools/networking.js +22 -22
- package/dist/tools/physics.js +4 -4
- package/dist/tools/playmode.js +5 -5
- package/dist/tools/playtest.js +2 -1
- package/dist/tools/prefabs.js +7 -3
- package/dist/tools/project.js +2 -2
- package/dist/tools/publishing.js +3 -3
- package/dist/tools/roundui.d.ts +22 -0
- package/dist/tools/roundui.js +88 -0
- package/dist/tools/scenes.js +2 -2
- package/dist/tools/scripts.js +3 -3
- package/dist/tools/statsachievements.d.ts +4 -0
- package/dist/tools/statsachievements.js +243 -0
- package/dist/tools/status.js +1 -1
- package/dist/tools/templates.js +7 -7
- package/dist/tools/ui.js +5 -5
- package/dist/tools/uifeedback.js +1 -1
- package/dist/tools/vehicles.d.ts +8 -0
- package/dist/tools/vehicles.js +98 -0
- package/dist/tools/visuals.js +8 -8
- package/dist/tools/workflow.d.ts +8 -0
- package/dist/tools/workflow.js +107 -0
- package/dist/tools/world.js +61 -58
- package/dist/tools/worldrender.d.ts +4 -0
- package/dist/tools/worldrender.js +183 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,19 @@ import { registerInteractionPackTools } from "./tools/interactionpack.js";
|
|
|
59
59
|
import { registerLootEconomyTools } from "./tools/looteconomy.js";
|
|
60
60
|
import { registerUiFeedbackTools } from "./tools/uifeedback.js";
|
|
61
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";
|
|
66
|
+
import { registerEconomySaveTools } from "./tools/economysave.js";
|
|
67
|
+
import { registerStatsAchievementsTools } from "./tools/statsachievements.js";
|
|
68
|
+
import { registerRoundUiTools } from "./tools/roundui.js";
|
|
69
|
+
import { registerWorldRenderTools } from "./tools/worldrender.js";
|
|
70
|
+
import { registerAiSystemsTools } from "./tools/aisystems.js";
|
|
71
|
+
import { registerGameplayRecorderTools } from "./tools/gameplayrecorder.js";
|
|
72
|
+
import { registerDialogueFxTools } from "./tools/dialoguefx.js";
|
|
73
|
+
import { registerMovieAuthoringTools } from "./tools/movieauthoring.js";
|
|
74
|
+
import { registerCinematicRecordingTools } from "./tools/cinematicrecording.js";
|
|
62
75
|
// ── CLI flags ──────────────────────────────────────────────────────
|
|
63
76
|
const args = process.argv.slice(2);
|
|
64
77
|
/** Read the package version from package.json, or return "unknown" on failure. */
|
|
@@ -78,68 +91,68 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
78
91
|
process.exit(0);
|
|
79
92
|
}
|
|
80
93
|
if (args.includes("--help") || args.includes("-h")) {
|
|
81
|
-
console.log(`sbox-mcp ${getVersion()} — MCP Server for s&box game engine
|
|
82
|
-
|
|
83
|
-
USAGE
|
|
84
|
-
node dist/index.js Start the MCP server (stdio transport)
|
|
85
|
-
node dist/index.js --help Show this help
|
|
86
|
-
node dist/index.js --version Show version
|
|
87
|
-
|
|
88
|
-
ENVIRONMENT VARIABLES
|
|
89
|
-
SBOX_BRIDGE_IPC_DIR IPC directory — MUST match the s&box addon's dir.
|
|
90
|
-
Default: <os tmpdir>/sbox-bridge-ipc
|
|
91
|
-
SBOX_BRIDGE_HOST Legacy/cosmetic — shown in get_bridge_status only
|
|
92
|
-
SBOX_BRIDGE_PORT Legacy/cosmetic — shown in get_bridge_status only
|
|
93
|
-
|
|
94
|
-
CONNECT TO CLAUDE CODE
|
|
95
|
-
claude mcp add sbox -- node /path/to/sbox-mcp-server/dist/index.js
|
|
96
|
-
|
|
97
|
-
TOOLS (
|
|
98
|
-
Project: get_project_info, list_project_files, read_file, write_file
|
|
99
|
-
Scripts: create_script, edit_script, delete_script, trigger_hotload
|
|
100
|
-
Scenes: list_scenes, load_scene, save_scene, create_scene
|
|
101
|
-
GameObjects: create/delete/duplicate/rename_gameobject, set_parent/enabled/transform
|
|
102
|
-
Components: get/set_property, get_all_properties, list_available_components, add_component_with_properties, set_prefab_ref
|
|
103
|
-
Hierarchy: get_scene_hierarchy (with maxDepth + rootId), get_selected_objects, select_object, focus_object
|
|
104
|
-
Assets: search_assets, list_asset_library, install_asset, get_asset_info
|
|
105
|
-
Materials: assign_model, create_material, assign_material, set_material_property
|
|
106
|
-
Audio: list_sounds, create_sound_event, assign_sound, play_sound_preview
|
|
107
|
-
Play Mode: start_play, stop_play, is_playing
|
|
108
|
-
Runtime: get/set_runtime_property, take_screenshot
|
|
109
|
-
Editor: undo, redo
|
|
110
|
-
Prefabs: create_prefab, instantiate_prefab, list_prefabs, get_prefab_info
|
|
111
|
-
Physics: add_physics, add_collider, add_joint, raycast
|
|
112
|
-
UI: create_razor_ui, add_screen_panel, add_world_panel
|
|
113
|
-
Templates: create_player_controller, create_npc_controller, create_game_manager, create_trigger_zone
|
|
114
|
-
Networking: add_network_helper, configure_network, get_network_status, network_spawn, set_ownership
|
|
115
|
-
Net Scripts: add_sync_property, add_rpc_method, create_networked_player, create_lobby_manager, create_network_events
|
|
116
|
-
Publishing: get_project_config, set_project_config, validate_project, set_project_thumbnail, get_package_details
|
|
117
|
-
World Gen: invoke_button, list_component_buttons, raycast_terrain, build_terrain_mesh
|
|
118
|
-
Map Edit: add_terrain_hill/clearing/trail, clear_terrain_features, sculpt_terrain
|
|
119
|
-
Caves: add_cave_waypoint, clear_cave_path
|
|
120
|
-
Forest: add_forest_poi/trail, set_forest_seed, clear_forest_pois, paint_forest_density
|
|
121
|
-
Placement: place_along_path
|
|
122
|
-
Discovery: describe_type, search_types, get_method_signature, find_in_project
|
|
123
|
-
Status: get_bridge_status
|
|
124
|
-
|
|
125
|
-
── New in v1.4.0 ───────────────────────────────────
|
|
126
|
-
Visual: add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look
|
|
127
|
-
Characters: spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression
|
|
128
|
-
Scene: snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance
|
|
129
|
-
Environment: scatter_props, randomize_transforms, group_objects
|
|
130
|
-
Utilities: find_objects, set_tint, replace_model, set_tags
|
|
131
|
-
VFX (exp): spawn_particle, create_particle_effect, add_trail, add_beam
|
|
132
|
-
|
|
133
|
-
── New in v1.5.0 ───────────────────────────────────
|
|
134
|
-
Diagnostics: read_log, get_compile_errors (MCP-server-side — work even if the editor crashed)
|
|
135
|
-
Camera: screenshot_from (AIM a shot at an object/point — take_screenshot is fixed to the Main Camera), frame_camera
|
|
136
|
-
Navigation: bake_navmesh, get_navmesh_path
|
|
137
|
-
Spatial: physics_overlap (volume counterpart to raycast)
|
|
138
|
-
Reflections: bake_reflections
|
|
139
|
-
Particles: spawn_vpcf (compiled .vpcf via LegacyParticleSystem — the supported particle path)
|
|
140
|
-
Console/Exec: console_run, execute_csharp (experimental)
|
|
141
|
-
Object utils: remove_component, get_tags
|
|
142
|
-
Docs search: search_docs, get_doc_page, list_doc_categories (MCP-server-side — official Facepunch/sbox-docs)
|
|
94
|
+
console.log(`sbox-mcp ${getVersion()} — MCP Server for s&box game engine
|
|
95
|
+
|
|
96
|
+
USAGE
|
|
97
|
+
node dist/index.js Start the MCP server (stdio transport)
|
|
98
|
+
node dist/index.js --help Show this help
|
|
99
|
+
node dist/index.js --version Show version
|
|
100
|
+
|
|
101
|
+
ENVIRONMENT VARIABLES
|
|
102
|
+
SBOX_BRIDGE_IPC_DIR IPC directory — MUST match the s&box addon's dir.
|
|
103
|
+
Default: <os tmpdir>/sbox-bridge-ipc
|
|
104
|
+
SBOX_BRIDGE_HOST Legacy/cosmetic — shown in get_bridge_status only
|
|
105
|
+
SBOX_BRIDGE_PORT Legacy/cosmetic — shown in get_bridge_status only
|
|
106
|
+
|
|
107
|
+
CONNECT TO CLAUDE CODE
|
|
108
|
+
claude mcp add sbox -- node /path/to/sbox-mcp-server/dist/index.js
|
|
109
|
+
|
|
110
|
+
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)
|
|
111
|
+
Project: get_project_info, list_project_files, read_file, write_file
|
|
112
|
+
Scripts: create_script, edit_script, delete_script, trigger_hotload
|
|
113
|
+
Scenes: list_scenes, load_scene, save_scene, create_scene
|
|
114
|
+
GameObjects: create/delete/duplicate/rename_gameobject, set_parent/enabled/transform
|
|
115
|
+
Components: get/set_property, get_all_properties, list_available_components, add_component_with_properties, set_prefab_ref
|
|
116
|
+
Hierarchy: get_scene_hierarchy (with maxDepth + rootId), get_selected_objects, select_object, focus_object
|
|
117
|
+
Assets: search_assets, list_asset_library, install_asset, get_asset_info
|
|
118
|
+
Materials: assign_model, create_material, assign_material, set_material_property
|
|
119
|
+
Audio: list_sounds, create_sound_event, assign_sound, play_sound_preview
|
|
120
|
+
Play Mode: start_play, stop_play, is_playing
|
|
121
|
+
Runtime: get/set_runtime_property, take_screenshot
|
|
122
|
+
Editor: undo, redo
|
|
123
|
+
Prefabs: create_prefab, instantiate_prefab, list_prefabs, get_prefab_info
|
|
124
|
+
Physics: add_physics, add_collider, add_joint, raycast
|
|
125
|
+
UI: create_razor_ui, add_screen_panel, add_world_panel
|
|
126
|
+
Templates: create_player_controller, create_npc_controller, create_game_manager, create_trigger_zone
|
|
127
|
+
Networking: add_network_helper, configure_network, get_network_status, network_spawn, set_ownership
|
|
128
|
+
Net Scripts: add_sync_property, add_rpc_method, create_networked_player, create_lobby_manager, create_network_events
|
|
129
|
+
Publishing: get_project_config, set_project_config, validate_project, set_project_thumbnail, get_package_details
|
|
130
|
+
World Gen: invoke_button, list_component_buttons, raycast_terrain, build_terrain_mesh
|
|
131
|
+
Map Edit: add_terrain_hill/clearing/trail, clear_terrain_features, sculpt_terrain
|
|
132
|
+
Caves: add_cave_waypoint, clear_cave_path
|
|
133
|
+
Forest: add_forest_poi/trail, set_forest_seed, clear_forest_pois, paint_forest_density
|
|
134
|
+
Placement: place_along_path
|
|
135
|
+
Discovery: describe_type, search_types, get_method_signature, find_in_project
|
|
136
|
+
Status: get_bridge_status
|
|
137
|
+
|
|
138
|
+
── New in v1.4.0 ───────────────────────────────────
|
|
139
|
+
Visual: add_light, set_fog, add_post_process, set_skybox, add_envmap_probe, apply_atmosphere, apply_post_fx_look
|
|
140
|
+
Characters: spawn_model, spawn_citizen, dress_citizen, set_bodygroup, pose_citizen, equip_model, set_look_at, add_ragdoll, set_expression
|
|
141
|
+
Scene: snap_to_ground, align_objects, distribute_objects, grid_duplicate, measure_distance
|
|
142
|
+
Environment: scatter_props, randomize_transforms, group_objects
|
|
143
|
+
Utilities: find_objects, set_tint, replace_model, set_tags
|
|
144
|
+
VFX (exp): spawn_particle, create_particle_effect, add_trail, add_beam
|
|
145
|
+
|
|
146
|
+
── New in v1.5.0 ───────────────────────────────────
|
|
147
|
+
Diagnostics: read_log, get_compile_errors (MCP-server-side — work even if the editor crashed)
|
|
148
|
+
Camera: screenshot_from (AIM a shot at an object/point — take_screenshot is fixed to the Main Camera), frame_camera
|
|
149
|
+
Navigation: bake_navmesh, get_navmesh_path
|
|
150
|
+
Spatial: physics_overlap (volume counterpart to raycast)
|
|
151
|
+
Reflections: bake_reflections
|
|
152
|
+
Particles: spawn_vpcf (compiled .vpcf via LegacyParticleSystem — the supported particle path)
|
|
153
|
+
Console/Exec: console_run, execute_csharp (experimental)
|
|
154
|
+
Object utils: remove_component, get_tags
|
|
155
|
+
Docs search: search_docs, get_doc_page, list_doc_categories (MCP-server-side — official Facepunch/sbox-docs)
|
|
143
156
|
`);
|
|
144
157
|
process.exit(0);
|
|
145
158
|
}
|
|
@@ -152,30 +165,47 @@ const server = new McpServer({
|
|
|
152
165
|
// server (the way other MCP servers like Supabase / TurboTax do). Use it to
|
|
153
166
|
// tell Claude how to work effectively with the bridge — the disciplines that
|
|
154
167
|
// are easy to skip without a reminder.
|
|
155
|
-
instructions: `You are working with the s&box Claude Bridge — a file-based IPC bridge into the s&box game engine editor.
|
|
156
|
-
|
|
157
|
-
To get good results:
|
|
158
|
-
|
|
159
|
-
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.
|
|
160
|
-
|
|
161
|
-
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.
|
|
162
|
-
|
|
163
|
-
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.
|
|
164
|
-
|
|
165
|
-
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.
|
|
166
|
-
|
|
167
|
-
5. Scene-mutating tools (create_gameobject, set_property, etc.) refuse during play mode and return a clear error. Stop play before making scene edits.
|
|
168
|
-
|
|
169
|
-
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.
|
|
170
|
-
|
|
171
|
-
If you're running inside Claude Code, install the companion plugin for the full workflow:
|
|
172
|
-
/plugin marketplace add LouSputthole/Sbox-Claude
|
|
173
|
-
/plugin install sbox-claude
|
|
174
|
-
|
|
168
|
+
instructions: `You are working with the s&box Claude Bridge — a file-based IPC bridge into the s&box game engine editor.
|
|
169
|
+
|
|
170
|
+
To get good results:
|
|
171
|
+
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
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.
|
|
175
|
+
|
|
176
|
+
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.
|
|
177
|
+
|
|
178
|
+
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.
|
|
179
|
+
|
|
180
|
+
5. Scene-mutating tools (create_gameobject, set_property, etc.) refuse during play mode and return a clear error. Stop play before making scene edits.
|
|
181
|
+
|
|
182
|
+
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.
|
|
183
|
+
|
|
184
|
+
If you're running inside Claude Code, install the companion plugin for the full workflow:
|
|
185
|
+
/plugin marketplace add LouSputthole/Sbox-Claude
|
|
186
|
+
/plugin install sbox-claude
|
|
187
|
+
|
|
175
188
|
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.`,
|
|
176
189
|
});
|
|
177
190
|
// Bridge client talks to the s&box editor via file IPC. host/port are cosmetic.
|
|
178
191
|
const bridge = new BridgeClient(process.env.SBOX_BRIDGE_HOST ?? "127.0.0.1", parseInt(process.env.SBOX_BRIDGE_PORT ?? "29015", 10));
|
|
192
|
+
// ── Lifeline mode (v2 migration) ───────────────────────────────────
|
|
193
|
+
// With the native editor MCP server carrying the full tool surface, this stdio
|
|
194
|
+
// server's long-term job is "editor-down diagnostics": the native server dies
|
|
195
|
+
// with the editor; these tools read the log / docs directly and don't. --lifeline
|
|
196
|
+
// registers ONLY that set by filtering server.tool() — everything else is a no-op.
|
|
197
|
+
const LIFELINE_TOOLS = new Set([
|
|
198
|
+
"read_log", "get_compile_errors", "search_docs", "get_doc_page",
|
|
199
|
+
"list_doc_categories", "run_self_test", "get_bridge_status",
|
|
200
|
+
]);
|
|
201
|
+
if (args.includes("--lifeline")) {
|
|
202
|
+
const realTool = server.tool.bind(server);
|
|
203
|
+
server.tool = (...toolArgs) => {
|
|
204
|
+
if (LIFELINE_TOOLS.has(toolArgs[0]))
|
|
205
|
+
return realTool(...toolArgs);
|
|
206
|
+
return undefined;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
179
209
|
// Register all tools
|
|
180
210
|
registerProjectTools(server, bridge);
|
|
181
211
|
registerScriptTools(server, bridge);
|
|
@@ -221,6 +251,19 @@ registerInteractionPackTools(server, bridge);
|
|
|
221
251
|
registerLootEconomyTools(server, bridge);
|
|
222
252
|
registerUiFeedbackTools(server, bridge);
|
|
223
253
|
registerCinematicsTools(server, bridge);
|
|
254
|
+
registerAuditTools(server, bridge);
|
|
255
|
+
registerBatchTools(server, bridge);
|
|
256
|
+
registerWorkflowTools(server, bridge);
|
|
257
|
+
registerVehicleTools(server, bridge);
|
|
258
|
+
registerEconomySaveTools(server, bridge);
|
|
259
|
+
registerStatsAchievementsTools(server, bridge);
|
|
260
|
+
registerRoundUiTools(server, bridge);
|
|
261
|
+
registerWorldRenderTools(server, bridge);
|
|
262
|
+
registerAiSystemsTools(server, bridge);
|
|
263
|
+
registerGameplayRecorderTools(server, bridge);
|
|
264
|
+
registerDialogueFxTools(server, bridge);
|
|
265
|
+
registerMovieAuthoringTools(server, bridge);
|
|
266
|
+
registerCinematicRecordingTools(server, bridge);
|
|
224
267
|
/** Start the MCP server on stdio and attempt initial Bridge connection. */
|
|
225
268
|
async function main() {
|
|
226
269
|
const transport = new StdioServerTransport();
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* AI & Systems pack — five scaffold generators:
|
|
4
|
+
*
|
|
5
|
+
* - create_needs_system sim/tycoon needs engine (decay -> Happiness + events)
|
|
6
|
+
* - create_utility_ai scored-action brain (abstract Action base + brain + 2 examples)
|
|
7
|
+
* - create_npc_schedule_brain daily-routine NPC bound to the day-night clock contract
|
|
8
|
+
* - create_event_bus typed LOCAL pub/sub static class (not a Component)
|
|
9
|
+
* - add_tts_voice TTS speaker over Sandbox.Speech.Synthesizer
|
|
10
|
+
*
|
|
11
|
+
* All write a .cs file into the project (scene/file-mutating; refused during play
|
|
12
|
+
* mode by the bridge dispatch). Every generated template was live-compile-verified
|
|
13
|
+
* (hotload + TypeLibrary-load) against the 2026-07 SDK on 2026-07-12.
|
|
14
|
+
*
|
|
15
|
+
* Mirrors the gamefeel.ts module shape: zod params, one bridge.send per tool,
|
|
16
|
+
* JSON.stringify(res.data) on success.
|
|
17
|
+
*/
|
|
18
|
+
// A world-space Vector3 accepted as EITHER an object {x,y,z} OR a comma string
|
|
19
|
+
// "x,y,z", passed through unchanged. The C# handler parses both forms.
|
|
20
|
+
const Vec3 = z
|
|
21
|
+
.union([
|
|
22
|
+
z.object({
|
|
23
|
+
x: z.number().describe("X"),
|
|
24
|
+
y: z.number().describe("Y"),
|
|
25
|
+
z: z.number().describe("Z"),
|
|
26
|
+
}),
|
|
27
|
+
z.string().describe('Comma string "x,y,z", e.g. "0,0,200"'),
|
|
28
|
+
])
|
|
29
|
+
.describe('A world-space Vector3 — object {x,y,z} OR comma string "x,y,z"');
|
|
30
|
+
export function registerAiSystemsTools(server, bridge) {
|
|
31
|
+
// ── create_needs_system ───────────────────────────────────────────
|
|
32
|
+
server.tool("create_needs_system", "Generate a sim/tycoon needs engine component: a [Property] list of need definitions (name, decay rate/s, critical threshold, weight) with per-need 0..100 values that decay over Time.Delta, Satisfy(name, amount) to restore, an aggregate Happiness (weighted mean, [Sync(FromHost)] when networked), and static OnNeedCritical (edge-triggered: fires once crossing below threshold, re-arms above) + OnHappinessChanged (>0.25-point moves) events. Returns {created, path, className, needs[], propertyNames[], note}. Next: trigger_hotload, get_compile_errors, then attach via targetId re-call or add_component_with_properties; drive from game code (e.g. a create_interactable that calls Satisfy). Limits: per-need values live on the simulating machine only (host) — sync per-need UI yourself via RPCs; events fire on the simulating machine only; networked default true means a no-session solo playtest won't tick (everything is a proxy) — pass networked:false to iterate solo. Refused during play mode; refuses to overwrite an existing file.", {
|
|
33
|
+
name: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Class/file name. Defaults to 'NeedsSystem'. Sanitized to a valid C# identifier."),
|
|
37
|
+
directory: z
|
|
38
|
+
.string()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Subdirectory under the project root for the .cs file. Defaults to 'Code'."),
|
|
41
|
+
needs: z
|
|
42
|
+
.array(z.object({
|
|
43
|
+
name: z.string().describe("Need name, e.g. 'Hunger' (the key for Satisfy/GetNeed)."),
|
|
44
|
+
decayPerSecond: z.number().optional().describe("Points lost per second on the 0..100 scale. Defaults to 0.5."),
|
|
45
|
+
criticalThreshold: z.number().optional().describe("OnNeedCritical fires when the value falls below this. Defaults to 20."),
|
|
46
|
+
weight: z.number().optional().describe("Contribution to the Happiness weighted mean. Defaults to 1."),
|
|
47
|
+
}))
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Need definitions baked as inspector-editable defaults. Defaults to the classic sim trio: Hunger(0.8/s), Energy(0.5/s), Fun(0.3/s)."),
|
|
50
|
+
networked: z
|
|
51
|
+
.boolean()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("true (default): host-authoritative (IsProxy guard) + [Sync(FromHost)] Happiness — needs a host session. false: local build that ticks in a solo playtest."),
|
|
54
|
+
targetId: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("GUID of a GameObject to attach the component to (only attaches if the type is already in the TypeLibrary — hotload first, then re-call or use add_component_with_properties)."),
|
|
58
|
+
}, async (params) => {
|
|
59
|
+
const res = await bridge.send("create_needs_system", params);
|
|
60
|
+
if (!res.success) {
|
|
61
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
62
|
+
}
|
|
63
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
64
|
+
});
|
|
65
|
+
// ── create_utility_ai ─────────────────────────────────────────────
|
|
66
|
+
server.tool("create_utility_ai", "Generate a utility-AI (scored-action) brain: one file with an abstract {name}Action : Component base (Score() 0..1 + Begin/Tick/End lifecycle), a sealed {name}Brain that every EvaluateInterval picks the highest-scoring sibling action (score × ScoreWeight, current action gets +HysteresisBonus so near-ties don't flip-flop), and two example actions — {name}IdleAction (constant fallback score) and {name}WanderAction (desire builds while idle, walks to random points by direct transform movement, no navmesh). How it differs from create_npc_brain: the FSM has a FIXED transition table; here behavior EMERGES from per-frame scores — add behaviors by subclassing the base on the same GameObject, no transition wiring. Returns {created, path, classNames[4], propertyNames[], note}. Next: trigger_hotload + get_compile_errors, attach the brain AND example actions to one GameObject (targetId attaches only the brain), verify in play mode via get_runtime_property CurrentActionName. Limits: networked default true = host-authoritative (won't tick in a no-session solo playtest — use networked:false); actions Tick on the simulating machine only. Refused during play mode; refuses to overwrite an existing file.", {
|
|
67
|
+
name: z
|
|
68
|
+
.string()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("System prefix — generates {name}Action / {name}Brain / {name}IdleAction / {name}WanderAction in {name}Ai.cs. Defaults to 'Utility'. Sanitized to a valid C# identifier."),
|
|
71
|
+
directory: z
|
|
72
|
+
.string()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Subdirectory under the project root for the .cs file. Defaults to 'Code'."),
|
|
75
|
+
evaluateInterval: z
|
|
76
|
+
.number()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Seconds between score evaluations (the active action still Ticks every frame). Defaults to 0.25."),
|
|
79
|
+
hysteresisBonus: z
|
|
80
|
+
.number()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Score bonus the current action gets during evaluation — stickiness that prevents flip-flopping between near-tied actions. Defaults to 0.15."),
|
|
83
|
+
moveSpeed: z
|
|
84
|
+
.number()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Example WanderAction walk speed in world units/s. Defaults to 80."),
|
|
87
|
+
wanderRadius: z
|
|
88
|
+
.number()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Example WanderAction roam radius around its start position. Defaults to 300."),
|
|
91
|
+
networked: z
|
|
92
|
+
.boolean()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("true (default): host-authoritative brain (IsProxy guard) + [Sync(FromHost)] CurrentActionName. false: local build for solo iteration."),
|
|
95
|
+
targetId: z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("GUID of a GameObject to attach the BRAIN to (actions must be added separately; only attaches if the type is already in the TypeLibrary — hotload first)."),
|
|
99
|
+
}, async (params) => {
|
|
100
|
+
const res = await bridge.send("create_utility_ai", params);
|
|
101
|
+
if (!res.success) {
|
|
102
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
103
|
+
}
|
|
104
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
105
|
+
});
|
|
106
|
+
// ── create_npc_schedule_brain ─────────────────────────────────────
|
|
107
|
+
server.tool("create_npc_schedule_brain", "Generate a daily-routine NPC brain: a [Property] list of schedule entries (startHour/endHour 0..24, taskName, target = named scene GameObject or fixed position), the hour read from any create_day_night_clock component (capability match: a float TimeOfDay property, same GameObject first then scene-wide) with an HONEST fallback to its own internal clock when none exists (check the generated UsingClockComponent bool), walking the NPC to the active entry's target and idling outside the schedule, plus a static OnTaskChanged(brain, taskName) event and [Sync(FromHost)] CurrentTask. Entries with endHour < startHour wrap past midnight. Returns {created, path, className, tasks[], propertyNames[], note}. Next: trigger_hotload + get_compile_errors, attach (targetId or add_component_with_properties), create the named target GameObjects (e.g. 'WorkSpot'), pair with create_day_night_clock for shared time, verify via get_runtime_property CurrentTask in play mode. Limits: default movement is a direct transform walk (walks through walls) — pass useNavMeshAgent:true for pathfinding (then bake_navmesh is REQUIRED); a clock with a different shape (e.g. 0..1 DayProgress) will NOT bind; networked default true won't tick in a no-session solo playtest (networked:false to iterate). Refused during play mode; refuses to overwrite an existing file.", {
|
|
108
|
+
name: z
|
|
109
|
+
.string()
|
|
110
|
+
.optional()
|
|
111
|
+
.describe("Class/file name. Defaults to 'NpcScheduleBrain'. Sanitized to a valid C# identifier."),
|
|
112
|
+
directory: z
|
|
113
|
+
.string()
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("Subdirectory under the project root for the .cs file. Defaults to 'Code'."),
|
|
116
|
+
schedule: z
|
|
117
|
+
.array(z.object({
|
|
118
|
+
startHour: z.number().describe("Entry start hour, 0..24 (inclusive)."),
|
|
119
|
+
endHour: z.number().describe("Entry end hour, 0..24 (exclusive). Smaller than startHour = wraps past midnight (e.g. 22 -> 6)."),
|
|
120
|
+
taskName: z.string().describe("Task label, e.g. 'Work' — surfaced via CurrentTask + OnTaskChanged."),
|
|
121
|
+
targetName: z.string().optional().describe("Named scene GameObject to walk to (case-insensitive; wins over targetPosition). Missing name = NPC idles."),
|
|
122
|
+
targetPosition: Vec3.optional().describe("Fixed world position to walk to, used when targetName is empty."),
|
|
123
|
+
}))
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Schedule entries baked as inspector-editable defaults. Defaults to Work 8-17 @ 'WorkSpot', Relax 17-22 @ 'HomeSpot' (idles/sleeps otherwise)."),
|
|
126
|
+
moveSpeed: z
|
|
127
|
+
.number()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe("Walk speed in world units/s. Defaults to 100."),
|
|
130
|
+
arriveDistance: z
|
|
131
|
+
.number()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Distance at which the NPC counts as arrived and idles at the spot. Defaults to 32."),
|
|
134
|
+
useNavMeshAgent: z
|
|
135
|
+
.boolean()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe("true: move via NavMeshAgent.MoveTo (real pathfinding — REQUIRES bake_navmesh or the NPC won't move). Defaults to false (direct transform walk, no navmesh needed, walks through walls)."),
|
|
138
|
+
fallbackDayLengthSeconds: z
|
|
139
|
+
.number()
|
|
140
|
+
.optional()
|
|
141
|
+
.describe("Internal fallback clock only: real seconds per 24 in-game hours when NO TimeOfDay clock component exists. Defaults to 600."),
|
|
142
|
+
fallbackStartHour: z
|
|
143
|
+
.number()
|
|
144
|
+
.optional()
|
|
145
|
+
.describe("Internal fallback clock only: starting hour 0..24. Defaults to 8."),
|
|
146
|
+
networked: z
|
|
147
|
+
.boolean()
|
|
148
|
+
.optional()
|
|
149
|
+
.describe("true (default): host-authoritative (IsProxy guard) + [Sync(FromHost)] CurrentTask. false: local build for solo iteration."),
|
|
150
|
+
targetId: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.describe("GUID of the NPC GameObject to attach to (only attaches if the type is already in the TypeLibrary — hotload first)."),
|
|
154
|
+
}, async (params) => {
|
|
155
|
+
const res = await bridge.send("create_npc_schedule_brain", params);
|
|
156
|
+
if (!res.success) {
|
|
157
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
158
|
+
}
|
|
159
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
160
|
+
});
|
|
161
|
+
// ── create_event_bus ──────────────────────────────────────────────
|
|
162
|
+
server.tool("create_event_bus", "Generate a typed LOCAL pub/sub event bus: a pure STATIC class (NOT a Component — nothing to place in the scene) with Subscribe<T>(owner, Action<T>), Unsubscribe(owner) (removes all of that owner's handlers across every event type), Publish<T>(evt) (synchronous, exact-type-T subscribers only, snapshot-iterated so handlers may subscribe/unsubscribe mid-publish), Count<T>() and Clear(), keyed by a plain Dictionary<Type, List<(object, Delegate)>> — plus a tiny example event record ({name}Ping). Decouples game systems: the quest system publishes 'EnemyDied', UI and achievements subscribe, neither knows the other. Returns {created, path, className, exampleEvent, api[], note}. Next: trigger_hotload + get_compile_errors, then Subscribe in components' OnStart and — REQUIRED — Unsubscribe(this) in OnDestroy: handler lists hold PLAIN references (no weak refs), so a component that never unsubscribes leaks itself for the scene's life; call Clear() on scene teardown. Limits: LOCAL only — Publish reaches the calling machine's subscribers, NOT other clients; for networked events pair with [Rpc.Broadcast]/[Rpc.Host] methods that Publish on arrival. No base-type dispatch (Publish<Base> won't reach Subscribe<Derived>). Refuses to overwrite an existing file; refused during play mode.", {
|
|
163
|
+
name: z
|
|
164
|
+
.string()
|
|
165
|
+
.optional()
|
|
166
|
+
.describe("Static class/file name. Defaults to 'EventBus'. The example event record is named {name}Ping. Sanitized to a valid C# identifier."),
|
|
167
|
+
directory: z
|
|
168
|
+
.string()
|
|
169
|
+
.optional()
|
|
170
|
+
.describe("Subdirectory under the project root for the .cs file. Defaults to 'Code'."),
|
|
171
|
+
}, async (params) => {
|
|
172
|
+
const res = await bridge.send("create_event_bus", params);
|
|
173
|
+
if (!res.success) {
|
|
174
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
175
|
+
}
|
|
176
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
177
|
+
});
|
|
178
|
+
// ── add_tts_voice ─────────────────────────────────────────────────
|
|
179
|
+
server.tool("add_tts_voice", "Generate a text-to-speech speaker component over Sandbox.Speech.Synthesizer (the OS speech engine — dynamic NPC dialog with zero recorded VO): call <class>.Say(\"text\") from game code and it builds a Synthesizer (TrySetVoice by exact VoiceName, else gender/age hint, else OS default) -> WithText -> WithRate -> Play(), returning a tracked SoundHandle — positional 3D parented to the speaker (default) or flat 2D, with stop-previous-on-say interruption, IsSpeaking, StopSpeaking(), and LogVoices() to enumerate installed OS voices. Returns {created, path, className, propertyNames[], note}. Next: trigger_hotload + get_compile_errors, attach (targetId or add_component_with_properties), then Say from game code — LOCAL audio only, wrap the Say call in an [Rpc.Broadcast] handler for everyone to hear. Limits & honesty: the editor cannot playtest audio, so RUNTIME speech is UNVERIFIED (the API surface compiles — verify with your ears in play mode); voices are machine/OS-specific and TrySetVoice is best-effort; LIPSYNC IS NOT AUTO-WIRED — s&box's Sandbox.LipSync component consumes a BaseSoundComponent, not the raw SoundHandle TTS produces, and Synthesizer.OnVisemeReached's delegate arg types can't be confirmed via reflection; enableVisemeData:true enables Handle.LipSync.Visemes for your own mouth-drive code (runtime-unverified). Refuses to overwrite an existing file; refused during play mode.", {
|
|
180
|
+
name: z
|
|
181
|
+
.string()
|
|
182
|
+
.optional()
|
|
183
|
+
.describe("Class/file name. Defaults to 'TtsSpeaker'. Sanitized to a valid C# identifier."),
|
|
184
|
+
directory: z
|
|
185
|
+
.string()
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Subdirectory under the project root for the .cs file. Defaults to 'Code'."),
|
|
188
|
+
voiceName: z
|
|
189
|
+
.string()
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("Exact installed OS voice name (machine-specific — the generated LogVoices() lists them at runtime). Empty = use voiceGender/voiceAge, or the OS default."),
|
|
192
|
+
voiceGender: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Voice gender hint used only when voiceName is empty (e.g. 'Female', 'Male'). Must be paired with voiceAge. Passed through unvalidated."),
|
|
196
|
+
voiceAge: z
|
|
197
|
+
.string()
|
|
198
|
+
.optional()
|
|
199
|
+
.describe("Voice age hint paired with voiceGender (e.g. 'Adult', 'Child', 'Senior'). Passed through unvalidated."),
|
|
200
|
+
rate: z
|
|
201
|
+
.number()
|
|
202
|
+
.optional()
|
|
203
|
+
.describe("Speaking rate offset (integer): negative = slower, positive = faster. Defaults to 0 (normal)."),
|
|
204
|
+
volume: z
|
|
205
|
+
.number()
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("Playback volume for spoken lines. Defaults to 1."),
|
|
208
|
+
positional: z
|
|
209
|
+
.boolean()
|
|
210
|
+
.optional()
|
|
211
|
+
.describe("true (default): 3D sound parented to the speaker GameObject (follows it). false: flat 2D voice on the listener (narrator/UI style)."),
|
|
212
|
+
stopPreviousOnSay: z
|
|
213
|
+
.boolean()
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("true (default): a new Say() fades out the still-playing previous line. false: lines overlap."),
|
|
216
|
+
stopFadeSeconds: z
|
|
217
|
+
.number()
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Fade-out duration used when interrupting/stopping a line. Defaults to 0.1."),
|
|
220
|
+
enableVisemeData: z
|
|
221
|
+
.boolean()
|
|
222
|
+
.optional()
|
|
223
|
+
.describe("true: sets Handle.LipSync.Enabled on each played line so custom mouth-drive code can read Handle.LipSync.Visemes. Runtime behavior unverified (editor can't playtest audio). Defaults to false."),
|
|
224
|
+
targetId: z
|
|
225
|
+
.string()
|
|
226
|
+
.optional()
|
|
227
|
+
.describe("GUID of the speaker GameObject to attach to (only attaches if the type is already in the TypeLibrary — hotload first)."),
|
|
228
|
+
}, async (params) => {
|
|
229
|
+
const res = await bridge.send("add_tts_voice", params);
|
|
230
|
+
if (!res.success) {
|
|
231
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }] };
|
|
232
|
+
}
|
|
233
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=aisystems.js.map
|
package/dist/tools/assets.js
CHANGED
|
@@ -28,19 +28,19 @@ export function registerAssetTools(server, bridge) {
|
|
|
28
28
|
};
|
|
29
29
|
});
|
|
30
30
|
// ── list_asset_library ───────────────────────────────────────────
|
|
31
|
-
server.tool("list_asset_library", "
|
|
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
|
|
35
|
+
.describe("Search term, case-insensitive substring match against asset NAMES"),
|
|
36
36
|
type: z
|
|
37
37
|
.string()
|
|
38
38
|
.optional()
|
|
39
|
-
.describe("
|
|
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
|
|
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')"),
|
package/dist/tools/audio.js
CHANGED
|
@@ -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
|
|
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
|
|
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("
|
|
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
|
-
.
|
|
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 (
|
|
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("
|
|
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
|