Editor/Mcp/BridgeGameObjectTools.cs

Editor-side bridge for GameObject editing tools. It declares a static class of Mcp tool wrapper methods that call McpGate.Run for scene operations like create, delete, align, duplicate, transform, tagging, selection, scattering, grid-duplicate, and queries of hierarchy/selection/bounds.

Networking
// AUTO-GENERATED by scripts/emit-mcp-wrappers.mjs — DO NOT EDIT.
// Regenerate: node scripts/extract-manifest.mjs && node scripts/emit-mcp-wrappers.mjs
// Source of truth: sbox-mcp-server/src/tools/ (zod schemas) → scripts/tools-manifest.json

using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Editor.Mcp;

/// <summary>
/// GameObject lifecycle, hierarchy, transforms, tags, selection, and bulk layout (align,
/// distribute, scatter, snap-to-ground, grid duplicate) in the open scene. Objects are referenced
/// by GUID from get_scene_hierarchy or find_objects.
/// </summary>
[McpToolset( "bridge_gameobject", "GameObject lifecycle, hierarchy, transforms, tags, selection, and bulk layout (align, distribute, scatter, snap-to-ground, grid duplicate) in the open scene. Objects are referenced by GUID from get_scene_hierarchy or find_objects." )]
public static class BridgeGameObjectTools
{
	/// <summary>
	/// Align several GameObjects on one axis so they share a coordinate. mode = first (match the first
	/// object), min, max, or average; defaults to first. Returns { aligned, axis, mode, target } —
	/// aligned is the object count and target the shared coordinate; verify positions with
	/// get_scene_hierarchy or a screenshot.
	/// </summary>
	/// <param name="ids">GUIDs of the GameObjects to align (&gt;= 2).</param>
	/// <param name="axis">Axis to align on. One of: x | y | z.</param>
	/// <param name="mode">Target coordinate to align to (default first). One of: first | min | max | average.</param>
	[McpTool( "align_objects" )]
	public static Task<object> AlignObjects( string[] ids, string axis, string mode = null )
		=> McpGate.Run( "align_objects", McpGate.Args( ( "ids", ids ), ( "axis", axis ), ( "mode", mode ) ) );

	/// <summary>
	/// Create a new GameObject in the active scene. Returns its GUID for future reference.
	/// </summary>
	/// <param name="name">Display name (e.g. 'Player', 'Enemy Spawn Point'). Defaults to 'New Object'.</param>
	/// <param name="position">World position. As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="rotation">World rotation. As "pitch,yaw,roll" degrees.</param>
	/// <param name="scale">Uniform scale (number) or per-axis scale — object {x,y,z} or comma string "x,y,z". As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="parent">GUID of parent GameObject. Omit for scene root.</param>
	[McpTool( "create_gameobject" )]
	public static Task<object> CreateGameobject( string name = null, string position = null, string rotation = null, string scale = null, string parent = null )
		=> McpGate.Run( "create_gameobject", McpGate.Args( ( "name", name ), ( "position", position ), ( "rotation", rotation ), ( "scale", scale ), ( "parent", parent ) ) );

	/// <summary>
	/// Delete a GameObject from the active scene by its GUID.
	/// </summary>
	/// <param name="id">GUID of the GameObject to delete.</param>
	[McpTool( "delete_gameobject" )]
	public static Task<object> DeleteGameobject( string id )
		=> McpGate.Run( "delete_gameobject", McpGate.Args( ( "id", id ) ) );

	/// <summary>
	/// Evenly space GameObjects along an axis between the lowest and highest (keeps the two ends fixed,
	/// spreads the rest evenly). Returns { distributed, axis, from, to } — the object count and the
	/// fixed end coordinates the rest were spread between.
	/// </summary>
	/// <param name="ids">GUIDs of the GameObjects to distribute (&gt;= 3).</param>
	/// <param name="axis">Axis to distribute along. One of: x | y | z.</param>
	[McpTool( "distribute_objects" )]
	public static Task<object> DistributeObjects( string[] ids, string axis )
		=> McpGate.Run( "distribute_objects", McpGate.Args( ( "ids", ids ), ( "axis", axis ) ) );

	/// <summary>
	/// Clone a GameObject with all its components. Returns { duplicated, original, gameObject } —
	/// gameObject.id is the clone's new GUID; pass it to set_transform / add_component_with_properties.
	/// If offset is omitted the clone lands exactly on top of the original.
	/// </summary>
	/// <param name="id">GUID of the GameObject to duplicate.</param>
	/// <param name="name">New name for the clone.</param>
	/// <param name="offset">Position offset from original so the clone doesn't overlap. As "x,y,z" (or JSON {x,y,z}).</param>
	[McpTool( "duplicate_gameobject" )]
	public static Task<object> DuplicateGameobject( string id, string name = null, string offset = null )
		=> McpGate.Run( "duplicate_gameobject", McpGate.Args( ( "id", id ), ( "name", name ), ( "offset", offset ) ) );

	/// <summary>
	/// Query the scene for GameObjects by name (case-insensitive substring), component type name,
	/// and/or tag — combine filters (AND). Returns {id,name} for matches (limit default 50, max 500).
	/// Read-only; works during play. Use it to get GUIDs to feed into
	/// align/distribute/set_tint/group/delete/etc.
	/// </summary>
	/// <param name="name">Name substring (case-insensitive).</param>
	/// <param name="component">Component type name, e.g. 'PointLight', 'SkinnedModelRenderer'.</param>
	/// <param name="tag">Tag the object must have.</param>
	/// <param name="limit">Max results (default 50, max 500).</param>
	[McpTool.ReadOnly( "find_objects" )]
	public static Task<object> FindObjects( string name = null, string component = null, string tag = null, int? limit = null )
		=> McpGate.Run( "find_objects", McpGate.Args( ( "name", name ), ( "component", component ), ( "tag", tag ), ( "limit", limit ) ) );

	/// <summary>
	/// Highlight a GameObject by selecting it in the editor. NOTE: s&amp;box exposes no dedicated focus
	/// API, so this only sets the selection — it does NOT move any camera (returns { focused, id, note
	/// } saying so). To actually point the viewport at an object use frame_camera; to aim a screenshot
	/// use screenshot_from.
	/// </summary>
	/// <param name="id">GUID of the GameObject to focus.</param>
	[McpTool( "focus_object" )]
	public static Task<object> FocusObject( string id )
		=> McpGate.Run( "focus_object", McpGate.Args( ( "id", id ) ) );

	/// <summary>
	/// Get a GameObject's world-space bounding box. Returns { id, name, center, size, extents, mins,
	/// maxs, radius, position, empty } — objects with no renderer report empty:true (bounds collapse to
	/// the world position) plus an explanatory note. Feed center/radius into screenshot_from or
	/// frame_camera to frame the object, or use mins/maxs for placement math (screenshot_orbit calls
	/// this internally).
	/// </summary>
	/// <param name="id">GUID of the GameObject to measure.</param>
	[McpTool.ReadOnly( "get_bounds" )]
	public static Task<object> GetBounds( string id )
		=> McpGate.Run( "get_bounds", McpGate.Args( ( "id", id ) ) );

	/// <summary>
	/// Get the scene tree — GameObjects with their names, GUIDs, components, and parent/child
	/// relationships. Pair maxDepth with rootId to drill into a subtree without paying for the whole
	/// scene.
	/// </summary>
	/// <param name="maxDepth">Maximum recursion depth. Defaults to 10. Use 1 or 2 for cheap top-level overviews.</param>
	/// <param name="rootId">Optional GUID of a GameObject to start traversal from. Omit to walk from the scene roots.</param>
	[McpTool.ReadOnly( "get_scene_hierarchy" )]
	public static Task<object> GetSceneHierarchy( int? maxDepth = null, string rootId = null )
		=> McpGate.Run( "get_scene_hierarchy", McpGate.Args( ( "maxDepth", maxDepth ), ( "rootId", rootId ) ) );

	/// <summary>
	/// Get the GameObjects currently selected by the user in the s&amp;box editor. Returns { count,
	/// selected } where each entry is a serialized GameObject (id, name, enabled, position, rotation,
	/// scale, components, childCount) — use the ids with set_transform, add_component_with_properties,
	/// etc. Handy for 'do X to what I have selected' requests.
	/// </summary>
	[McpTool.ReadOnly( "get_selected_objects" )]
	public static Task<object> GetSelectedObjects()
		=> McpGate.Run( "get_selected_objects", McpGate.Args() );

	/// <summary>
	/// Read the tags currently on a GameObject. (Pair with set_tags to add/remove/clear, and
	/// find_objects to query by tag.).
	/// </summary>
	/// <param name="id">GUID of the GameObject.</param>
	[McpTool.ReadOnly( "get_tags" )]
	public static Task<object> GetTags( string id )
		=> McpGate.Run( "get_tags", McpGate.Args( ( "id", id ) ) );

	/// <summary>
	/// Clone a GameObject into an X/Y/Z grid with fixed spacing (the original stays in place). Each
	/// count is clamped to 50 and total clones to 500. Great for fences, crates, pillars, foliage rows.
	/// </summary>
	/// <param name="id">GUID of the GameObject to clone.</param>
	/// <param name="countX">Copies along X (default 1).</param>
	/// <param name="countY">Copies along Y (default 1).</param>
	/// <param name="countZ">Copies along Z (default 1).</param>
	/// <param name="spacing">Spacing between copies per axis (default 100,100,100). As "x,y,z" (or JSON {x,y,z}).</param>
	[McpTool( "grid_duplicate" )]
	public static Task<object> GridDuplicate( string id, int? countX = null, int? countY = null, int? countZ = null, string spacing = null )
		=> McpGate.Run( "grid_duplicate", McpGate.Args( ( "id", id ), ( "countX", countX ), ( "countY", countY ), ( "countZ", countZ ), ( "spacing", spacing ) ) );

	/// <summary>
	/// Parent a set of GameObjects under a new empty group object (placed at their centroid) — tidies
	/// the hierarchy and lets you move/rotate them together.
	/// </summary>
	/// <param name="ids">GUIDs of the GameObjects to group.</param>
	/// <param name="name">Name for the group object (default 'Group').</param>
	[McpTool( "group_objects" )]
	public static Task<object> GroupObjects( string[] ids, string name = null )
		=> McpGate.Run( "group_objects", McpGate.Args( ( "ids", ids ), ( "name", name ) ) );

	/// <summary>
	/// Measure the distance between two points or two GameObjects. Provide a/b as {x,y,z} or idA/idB as
	/// GUIDs. Returns straight-line distance, horizontal (ground) distance, and the delta vector.
	/// Read-only (works during play).
	/// </summary>
	/// <param name="a">First point {x,y,z}. As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="b">Second point {x,y,z}. As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="idA">First GameObject GUID (overrides a).</param>
	/// <param name="idB">Second GameObject GUID (overrides b).</param>
	[McpTool.ReadOnly( "measure_distance" )]
	public static Task<object> MeasureDistance( string a = null, string b = null, string idA = null, string idB = null )
		=> McpGate.Run( "measure_distance", McpGate.Args( ( "a", a ), ( "b", b ), ( "idA", idA ), ( "idB", idB ) ) );

	/// <summary>
	/// Add natural variation to existing objects: random yaw and/or random uniform scale within a
	/// range. Great for breaking up repetition in placed foliage/rocks/crates. Seeded — the same seed
	/// reproduces the same layout. Returns { randomized, seed } (the count of objects changed); scale
	/// only varies when scaleMax &gt; scaleMin.
	/// </summary>
	/// <param name="ids">GUIDs of the GameObjects to randomize.</param>
	/// <param name="randomYaw">Randomize Z rotation (default true).</param>
	/// <param name="scaleMin">Min uniform scale (default 1).</param>
	/// <param name="scaleMax">Max uniform scale (default 1; set &gt;min to vary).</param>
	/// <param name="seed">PRNG seed (default 1).</param>
	[McpTool( "randomize_transforms" )]
	public static Task<object> RandomizeTransforms( string[] ids, bool? randomYaw = null, double? scaleMin = null, double? scaleMax = null, int? seed = null )
		=> McpGate.Run( "randomize_transforms", McpGate.Args( ( "ids", ids ), ( "randomYaw", randomYaw ), ( "scaleMin", scaleMin ), ( "scaleMax", scaleMax ), ( "seed", seed ) ) );

	/// <summary>
	/// Change the display name of a GameObject identified by its GUID (the GUID itself never changes,
	/// so existing references stay valid). Returns { renamed, id, oldName, newName }. Name-based
	/// lookups (e.g. find_objects) will see the new name immediately.
	/// </summary>
	/// <param name="id">GUID of the GameObject.</param>
	/// <param name="name">New display name.</param>
	[McpTool( "rename_gameobject" )]
	public static Task<object> RenameGameobject( string id, string name )
		=> McpGate.Run( "rename_gameobject", McpGate.Args( ( "id", id ), ( "name", name ) ) );

	/// <summary>
	/// Swap the model on one object (id) or many (ids) — e.g. retheme a row of props in one call.
	/// </summary>
	/// <param name="model">New model path, e.g. 'models/dev/sphere.vmdl'.</param>
	/// <param name="id">Single GameObject GUID.</param>
	/// <param name="ids">Multiple GameObject GUIDs.</param>
	[McpTool( "replace_model" )]
	public static Task<object> ReplaceModel( string model, string id = null, string[] ids = null )
		=> McpGate.Run( "replace_model", McpGate.Args( ( "model", model ), ( "id", id ), ( "ids", ids ) ) );

	/// <summary>
	/// Scatter N copies of a model randomly within a radius around a center point — instant foliage,
	/// rocks, debris. Each copy gets a random yaw and (by default) is snapped to the ground. Seeded for
	/// reproducibility; copies are grouped under one parent by default. Count capped at 300. Returns {
	/// scattered, groupId, seed } — individual prop GUIDs are not returned, so use groupId with
	/// get_scene_hierarchy (rootId) to enumerate them, or with set_transform/delete_gameobject to
	/// move/remove the whole batch.
	/// </summary>
	/// <param name="model">Model path to scatter, e.g. 'models/dev/box.vmdl'.</param>
	/// <param name="center">Centre of the scatter area (default origin). As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="radius">Scatter radius in units (default 256).</param>
	/// <param name="count">How many to place (default 10, max 300).</param>
	/// <param name="randomYaw">Randomly rotate each around Z (default true).</param>
	/// <param name="snapToGround">Raycast each onto the surface below (default true).</param>
	/// <param name="scaleMin">Min uniform scale (default 1).</param>
	/// <param name="scaleMax">Max uniform scale (default 1; set &gt;min for size variation).</param>
	/// <param name="tint">Tint applied to every copy — object {r,g,b,a} or comma string "r,g,b,a". As "r,g,b[,a]" (0-1 floats).</param>
	/// <param name="seed">PRNG seed for a reproducible layout (default 1).</param>
	/// <param name="group">Parent all copies under one group object (default true).</param>
	/// <param name="name">Base name for the props/group (default 'Prop').</param>
	[McpTool( "scatter_props" )]
	public static Task<object> ScatterProps( string model, string center = null, double? radius = null, int? count = null, bool? randomYaw = null, bool? snapToGround = null, double? scaleMin = null, double? scaleMax = null, string tint = null, int? seed = null, bool? group = null, string name = null )
		=> McpGate.Run( "scatter_props", McpGate.Args( ( "model", model ), ( "center", center ), ( "radius", radius ), ( "count", count ), ( "randomYaw", randomYaw ), ( "snapToGround", snapToGround ), ( "scaleMin", scaleMin ), ( "scaleMax", scaleMax ), ( "tint", tint ), ( "seed", seed ), ( "group", group ), ( "name", name ) ) );

	/// <summary>
	/// Select a GameObject in the editor (highlights it in the hierarchy and scene view). Replaces the
	/// current selection unless addToSelection=true. Returns { selected, id }; confirm the result with
	/// get_selected_objects.
	/// </summary>
	/// <param name="id">GUID of the GameObject to select.</param>
	/// <param name="addToSelection">If true, adds to current selection instead of replacing it.</param>
	[McpTool( "select_object" )]
	public static Task<object> SelectObject( string id, bool? addToSelection = null )
		=> McpGate.Run( "select_object", McpGate.Args( ( "id", id ), ( "addToSelection", addToSelection ) ) );

	/// <summary>
	/// Enable or disable a GameObject (disabled objects are invisible and inactive, including their
	/// components and children). Returns { id, enabled } confirming the new state.
	/// </summary>
	/// <param name="id">GUID of the GameObject.</param>
	/// <param name="enabled">true to enable, false to disable.</param>
	[McpTool( "set_enabled" )]
	public static Task<object> SetEnabled( string id, bool enabled )
		=> McpGate.Run( "set_enabled", McpGate.Args( ( "id", id ), ( "enabled", enabled ) ) );

	/// <summary>
	/// Reparent a GameObject. Set parentId to null or omit to move to scene root.
	/// </summary>
	/// <param name="id">GUID of the GameObject to reparent.</param>
	/// <param name="parentId">GUID of the new parent. Null or omitted = scene root.</param>
	[McpTool( "set_parent" )]
	public static Task<object> SetParent( string id, string parentId = null )
		=> McpGate.Run( "set_parent", McpGate.Args( ( "id", id ), ( "parentId", parentId ) ) );

	/// <summary>
	/// Add, remove, and/or clear gameplay tags on one object (id) or many (ids). Tags drive collision
	/// groups, queries, and triggers.
	/// </summary>
	/// <param name="id">Single GameObject GUID.</param>
	/// <param name="ids">Multiple GameObject GUIDs.</param>
	/// <param name="add">Tags to add.</param>
	/// <param name="remove">Tags to remove.</param>
	/// <param name="clear">Remove all existing tags first.</param>
	[McpTool( "set_tags" )]
	public static Task<object> SetTags( string id = null, string[] ids = null, string[] add = null, string[] remove = null, bool? clear = null )
		=> McpGate.Run( "set_tags", McpGate.Args( ( "id", id ), ( "ids", ids ), ( "add", add ), ( "remove", remove ), ( "clear", clear ) ) );

	/// <summary>
	/// Set the renderer tint colour on one object (id) or many (ids) at once. Works on any
	/// ModelRenderer/SkinnedModelRenderer. Pass the colour as "tint" (or its alias "color"); each
	/// accepts an object {r,g,b,a} OR a comma string "r,g,b,a".
	/// </summary>
	/// <param name="id">Single GameObject GUID.</param>
	/// <param name="ids">Multiple GameObject GUIDs.</param>
	/// <param name="tint">Tint colour to apply (object or comma string). As "r,g,b[,a]" (0-1 floats).</param>
	/// <param name="color">Alias for "tint" (object or comma string). As "r,g,b[,a]" (0-1 floats).</param>
	[McpTool( "set_tint" )]
	public static Task<object> SetTint( string id = null, string[] ids = null, string tint = null, string color = null )
		=> McpGate.Run( "set_tint", McpGate.Args( ( "id", id ), ( "ids", ids ), ( "tint", tint ), ( "color", color ) ) );

	/// <summary>
	/// Set position, rotation, and/or scale on a GameObject. Only provided values are changed; values
	/// apply in world space unless local=true. Returns { transformed, gameObject } with the full
	/// serialized object (id, name, position, rotation, scale, components) so you can verify what
	/// actually applied.
	/// </summary>
	/// <param name="id">GUID of the GameObject.</param>
	/// <param name="position">New position. As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="rotation">New rotation. As "pitch,yaw,roll" degrees.</param>
	/// <param name="scale">New scale — uniform number, per-axis object {x,y,z}, or comma string "x,y,z". As "x,y,z" (or JSON {x,y,z}).</param>
	/// <param name="local">If true, values are in local space. Default is world space.</param>
	[McpTool( "set_transform" )]
	public static Task<object> SetTransform( string id, string position = null, string rotation = null, string scale = null, bool? local = null )
		=> McpGate.Run( "set_transform", McpGate.Args( ( "id", id ), ( "position", position ), ( "rotation", rotation ), ( "scale", scale ), ( "local", local ) ) );

	/// <summary>
	/// Drop a GameObject straight down onto the surface below it (physics raycast). Works best on
	/// collider-less props (an object with its own collider may self-hit). Optional offset lifts it off
	/// the surface. Returns { snapped, groundZ, gameObject } with the object's updated transform — or {
	/// snapped: false, reason } (not an error) when no ground was hit below.
	/// </summary>
	/// <param name="id">GUID of the GameObject to snap.</param>
	/// <param name="offset">Height above the surface to place it (default 0).</param>
	/// <param name="startHeight">How far above the object to start the trace (default 2000).</param>
	/// <param name="maxDistance">Max trace distance downward (default 20000).</param>
	[McpTool( "snap_to_ground" )]
	public static Task<object> SnapToGround( string id, double? offset = null, double? startHeight = null, double? maxDistance = null )
		=> McpGate.Run( "snap_to_ground", McpGate.Args( ( "id", id ), ( "offset", offset ), ( "startHeight", startHeight ), ( "maxDistance", maxDistance ) ) );
}