Editor/Mcp/BridgeValidationTools.cs

Editor toolset wrapper that exposes a set of read-only MCP (management control plane) bridge validation commands to the editor. Each static method forwards a named tool command and arguments to McpGate.Run to perform linting, scene/save/asset inspection, networking and sandbox whitelist scans, and service queries.

NetworkingFile Access
// 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>
/// Lint and validate the project: networking footguns, sandbox whitelist violations, Razor
/// transpiler footguns, scene setup issues, publishing readiness, save-file inspection, and
/// networked-object state dumps.
/// </summary>
[McpToolset( "bridge_validation", "Lint and validate the project: networking footguns, sandbox whitelist violations, Razor transpiler footguns, scene setup issues, publishing readiness, save-file inspection, and networked-object state dumps." )]
public static class BridgeValidationTools
{
	/// <summary>
	/// 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).
	/// </summary>
	/// <param name="limit">Max issues to return (default 100, max 500). total still counts everything.</param>
	/// <param name="scanFiles">Include the .scene/.prefab file scan for missing prefab references. Default true.</param>
	[McpTool.ReadOnly( "find_broken_references" )]
	public static Task<object> FindBrokenReferences( int? limit = null, bool? scanFiles = null )
		=> McpGate.Run( "find_broken_references", McpGate.Args( ( "limit", limit ), ( "scanFiles", scanFiles ) ) );

	/// <summary>
	/// Inspect the live networking contract of a GameObject. Returns {id, name, network: {active,
	/// isProxy, isOwner, isCreator, ownerId, ownerSteamId, ownerTransfer, orphaned, flags}, components:
	/// [{component, fields: [{name, type, isSync, syncFlags, value}]}]} — by default only [Sync]-marked
	/// fields are listed (components with none are omitted). Unlike get_network_status (session-only),
	/// this is per-object — the way to verify a host-authoritative or ownership change actually
	/// replicated; works in edit or play mode. Follow up with set_ownership to change the owner, or
	/// networking_lint to find the code-level cause of a bad [Sync] value.
	/// </summary>
	/// <param name="id">GUID of the GameObject to inspect.</param>
	/// <param name="allProps">Include all component properties, not just [Sync]-marked ones.</param>
	[McpTool.ReadOnly( "inspect_networked_object" )]
	public static Task<object> InspectNetworkedObject( string id, bool allProps = false )
		=> McpGate.Run( "inspect_networked_object", McpGate.Args( ( "id", id ), ( "allProps", allProps ) ) );

	/// <summary>
	/// Static-scan the project's C# for the highest-frequency networking/authority bugs: a mutator that
	/// writes a [Sync] field with no IsProxy/Networking.IsHost guard; money/health/score-shaped fields
	/// marked plain [Sync] (should be SyncFlags.FromHost); List&lt;&gt;/Dictionary&lt;&gt; marked
	/// [Sync] (should be NetList/NetDictionary); [Sync] fields typed Connection/GameObject (sync a Guid
	/// instead); [Rpc.Host] methods that mutate without re-checking Rpc.Caller; and component swaps /
	/// reflection writes missing Network.Refresh(). Returns findings with file:line + the suggested
	/// fix.
	/// </summary>
	/// <param name="path">Optional sub-path under the project (e.g. 'Code/Player') to scope the scan; omit for the whole project.</param>
	[McpTool.ReadOnly( "networking_lint" )]
	public static Task<object> NetworkingLint( string path = null )
		=> McpGate.Run( "networking_lint", McpGate.Args( ( "path", path ) ) );

	/// <summary>
	/// Static-scan .razor and .razor.scss files for the silent footguns that crash the Razor transpiler
	/// or stylesheet engine with no useful error message: switch expressions inside @code blocks (use
	/// if/else instead), non-ASCII/emoji inside @code (move to markup or a string constant),
	/// PanelComponent subclasses missing a BuildHash override (panel never re-renders), and root
	/// uppercase type-selector rules in .razor.scss (silently skipped -- use a class selector like
	/// .my-panel). Returns { scanned, findings: [{file, line, match, advice}], clean } matching the
	/// sandbox_lint shape.
	/// </summary>
	/// <param name="directory">Subdirectory under the project root to scan (e.g. 'UI', 'Code'). Defaults to 'Code'.</param>
	[McpTool.ReadOnly( "razor_lint" )]
	public static Task<object> RazorLint( string directory = null )
		=> McpGate.Run( "razor_lint", McpGate.Args( ( "directory", directory ) ) );

	/// <summary>
	/// Static-scan the project's C# for s&amp;box sandbox whitelist violations BEFORE they cause
	/// compile errors: System.MathF (use MathX), System.Math (use MathX), Array.Clone() (use
	/// .ToArray()), System.Net / raw sockets (use Sandbox.Http), System.IO.File (use FileSystem.Data),
	/// and raw System.Threading.Thread (use async/Task or GameTask). Returns { scanned, findings:
	/// [{file, line, match, advice}], clean }. Scope to a subdirectory with the directory param.
	/// </summary>
	/// <param name="directory">Subdirectory under the project root to scan (e.g. 'Code', 'Code/Player'). Defaults to 'Code'.</param>
	[McpTool.ReadOnly( "sandbox_lint" )]
	public static Task<object> SandboxLint( string directory = null )
		=> McpGate.Run( "sandbox_lint", McpGate.Args( ( "directory", directory ) ) );

	/// <summary>
	/// Inspect the game's FileSystem.Data save files — the assistant is otherwise blind to persisted
	/// state. action='list' (default) returns `directories` and `files` [{name, path, size}] under
	/// `path` (omit path for the Data root); action='read' returns {path, length, content}, truncating
	/// content at 60,000 chars; action='diff' compares two save files key-by-key, returning `diffCount`
	/// and up to 200 `diffs` [{key, change: added|removed|changed}]. Use to verify a save actually
	/// wrote, debug a load/migration, or confirm a sanitize/clamp ran.
	/// </summary>
	/// <param name="action">'list' (default) enumerates a folder; 'read' dumps one file's JSON; 'diff' compares `path` vs `pathB`. One of: list | read | diff. Default: "list".</param>
	/// <param name="path">File or folder path under FileSystem.Data (e.g. 'lumber_corp2_progress' or '&lt;folder&gt;/steam_123.json').</param>
	/// <param name="pathB">Second file path for action='diff'.</param>
	[McpTool.ReadOnly( "save_inspect" )]
	public static Task<object> SaveInspect( string action = "list", string path = null, string pathB = null )
		=> McpGate.Run( "save_inspect", McpGate.Args( ( "action", action ), ( "path", path ), ( "pathB", pathB ) ) );

	/// <summary>
	/// Validate the active scene for the silent setup footguns that break controllers/physics/cameras:
	/// no CameraComponent, no player controller, multiple root Rigidbodies, a Rigidbody with
	/// MotionEnabled=false fighting a kinematic root, IsTrigger colliders that Scene.Trace will ignore,
	/// child Rigidbodies breaking collider binding, and missing required child anchors. Returns each
	/// issue with the GameObject and the exact fix.
	/// </summary>
	[McpTool.ReadOnly( "scene_validate" )]
	public static Task<object> SceneValidate()
		=> McpGate.Run( "scene_validate", McpGate.Args() );

	/// <summary>
	/// Read from Sandbox.Services — the cloud stats/leaderboard layer many games use as their real DB.
	/// action='stats' with `name` returns the local player's stat {ident, value, sum, min, max,
	/// lastValue, valueString}; without `name` it returns only the package ident plus a usage note (it
	/// does NOT list stat definitions). action='leaderboard' (name required) returns {board,
	/// displayName, totalEntries, count, entries} with at most `limit` entries (default 10). Read-only;
	/// use to verify a Stats.Increment/SetValue path or a leaderboard wired correctly.
	/// </summary>
	/// <param name="action">'stats' (default) reads a local-player stat by `name`; 'leaderboard' fetches a board's top entries. One of: stats | leaderboard. Default: "stats".</param>
	/// <param name="name">Stat name (action='stats') or leaderboard/board name (action='leaderboard').</param>
	/// <param name="limit">Max leaderboard entries to return.</param>
	[McpTool.ReadOnly( "services_query" )]
	public static Task<object> ServicesQuery( string action = "stats", string name = null, int limit = 10 )
		=> McpGate.Run( "services_query", McpGate.Args( ( "action", action ), ( "name", name ), ( "limit", limit ) ) );

	/// <summary>
	/// Validate that the project is ready for publishing. Runs four checks: .sbproj exists, at least
	/// one scene, project Ident set, project Title set. Returns { valid, issueCount, issues, checks } —
	/// issues are human-readable problems and each checks entry has { check, pass, detail }; fix
	/// metadata gaps with set_project_config (it does NOT check compile errors — use get_compile_errors
	/// for that).
	/// </summary>
	[McpTool.ReadOnly( "validate_project" )]
	public static Task<object> ValidateProject()
		=> McpGate.Run( "validate_project", McpGate.Args() );
}