Editor/Tools/EngineControlTools.cs
#nullable enable
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;
namespace Editor.Mcp
{
public static partial class SboxMcpAssistant
{
[McpTool("pocketknife_switch_editor_tab")]
public static string SwitchEditorTab([Description("Tab name, e.g. scene, code, assetbrowser")] string tabName)
{
try
{
var tab = (tabName ?? "").Trim().ToLowerInvariant();
if (string.IsNullOrWhiteSpace(tab) || tab.Any(char.IsWhiteSpace)) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "editor.invalid_tab", "A single editor tab name is required."));
if (tab.Contains("scene", StringComparison.OrdinalIgnoreCase))
{
var session = EditorRuntime.GetStaticProperty("Editor.SceneEditorSession", "Active");
if (session is null) return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Unavailable, "editor.scene_tab_unavailable", "No scene editor session is active."));
EditorRuntime.InvokeInstance(session, "MakeActive", true);
EditorRuntime.InvokeInstance(session, "BringToFront");
var dock = EditorRuntime.GetMember(session, "SceneDock");
var activeSession = EditorRuntime.GetStaticProperty("Editor.SceneEditorSession", "Active");
var verified = ReferenceEquals(session, activeSession);
return verified ? McpJson.Write(ToolOutcome<object>.Ready(new { Tab = "scene", Scene = EditorRuntime.GetMember(EditorRuntime.GetMember(session, "Scene"), "Name")?.ToString(), DockType = dock?.GetType().FullName, IsActiveWindow = EditorRuntime.GetMember(dock, "IsActiveWindow"), Verified = true })) : McpJson.Write(ToolOutcome<object>.Degraded(new { Tab = "scene", Verified = false }, "editor.scene_activation_unverified", "The scene session accepted activation but the active-session postcondition did not match.", "Use the live editor scene/list_scenes tools to verify the active tab."));
}
if (tab.Contains("code", StringComparison.OrdinalIgnoreCase))
{
var opened = EditorRuntime.InvokeStatic("Editor.CodeEditor", "OpenSolution");
var current = EditorRuntime.GetStaticProperty("Editor.CodeEditor", "Current");
return current is not null ? McpJson.Write(ToolOutcome<object>.Ready(new { Tab = "code", Opened = true, EditorType = current.GetType().FullName, Verified = true })) : McpJson.Write(ToolOutcome<object>.Failure(new { Tab = "code", Opened = opened is not null }, ToolStatus.Unavailable, "editor.code_tab_unavailable", "The code editor did not expose an active editor after OpenSolution."));
}
var mainWindow = EditorRuntime.GetStaticField("Editor.EditorMainWindow", "Current");
var dockManager = EditorRuntime.GetMember(mainWindow, "DockManager");
if (dockManager is null) return McpJson.Write(ToolOutcome<object>.Failure(new { Tab = tab }, ToolStatus.Unavailable, "editor.dock_manager_unavailable", "The editor main window did not expose its dock manager."));
var dockTypes = EditorRuntime.GetMember(dockManager, "DockTypes") as IEnumerable;
var candidates = new System.Collections.Generic.List<string>();
if (dockTypes is not null)
{
foreach (var dockInfo in dockTypes)
{
var title = EditorRuntime.GetMember(dockInfo, "Title")?.ToString();
if (!string.IsNullOrWhiteSpace(title)) candidates.Add(title);
}
}
var match = candidates.FirstOrDefault(x => x.Contains(tab, StringComparison.OrdinalIgnoreCase) || (tab.Contains("asset") && x.Contains("asset", StringComparison.OrdinalIgnoreCase)) || (tab.Contains("material") && x.Contains("material", StringComparison.OrdinalIgnoreCase)) || (tab.Contains("hammer") && x.Contains("hammer", StringComparison.OrdinalIgnoreCase)));
if (match is null) return McpJson.Write(ToolOutcome<object>.Failure(new { Tab = tab, AvailableDocks = candidates.Take(100).ToArray() }, ToolStatus.Unavailable, "editor.dock_not_found", $"No editor dock matched '{tab}'.", remediation: "Use a dock title returned by the editor's dock registry or the live editor UI tools."));
var raised = EditorRuntime.InvokeInstance(dockManager, "RaiseDock", match) as bool?;
if (raised != true)
{
var dockArea = EditorRuntime.FindType("Editor.DockArea");
var center = dockArea is null ? null : Enum.Parse(dockArea, "Center");
EditorRuntime.InvokeInstance(dockManager, "OpenDock", match, center, null);
raised = EditorRuntime.InvokeInstance(dockManager, "RaiseDock", match) as bool?;
}
return raised == true ? McpJson.Write(ToolOutcome<object>.Ready(new { Tab = tab, DockTitle = match, Raised = true, Verified = true })) : McpJson.Write(ToolOutcome<object>.Degraded(new { Tab = tab, DockTitle = match, Raised = false }, "editor.dock_activation_unverified", "The matching editor dock was found, but the editor did not confirm it became active.", "Use the live editor UI bridge to inspect the active dock."));
}
catch (Exception ex) { return McpJson.Write(ToolOutcome<object>.Failure(null, ToolStatus.Failed, "editor.tab_failed", "Editor tab switch failed.", ex)); }
}
}
}