Skip to main content
Glama
SceneSetupCommand.cs64.3 kB
using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; #if UNITY_EDITOR using UnityEditor; using UnityEditor.SceneManagement; #endif namespace LocalMcp.UnityServer { /// <summary> /// Provides scene management operations for Unity MCP Server. /// Handles scene structure retrieval, initialization, and GameObject creation. /// </summary> public static class SceneSetupCommand { /// <summary> /// Lists the current scene structure with all GameObjects and their components. /// </summary> /// <param name="paramsJson">JSON parameters containing optional maxDepth (default: 2)</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response containing scene structure</returns> public static string ListScene(string paramsJson, object id) { try { int maxDepth = 2; // Default depth limit if (!string.IsNullOrEmpty(paramsJson)) { try { var parameters = JsonUtility.FromJson<SceneListParams>(paramsJson); if (parameters.maxDepth > 0) { maxDepth = parameters.maxDepth; } } catch { // If parsing fails, use default } } Scene scene = SceneManager.GetActiveScene(); GameObject[] rootObjects = scene.GetRootGameObjects(); List<GameObjectData> rootObjectsData = new List<GameObjectData>(); foreach (GameObject rootObj in rootObjects) { rootObjectsData.Add(SerializeGameObject(rootObj, rootObj.name, 0, maxDepth)); } var result = new SceneListResult { sceneName = scene.name, rootObjects = rootObjectsData.ToArray(), maxDepth = maxDepth }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] ListScene error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to list scene: {e.Message}", id); } } /// <summary> /// Overload for backward compatibility (no parameters) /// </summary> public static string ListScene(object id) { return ListScene(null, id); } /// <summary> /// Sets up a scene with optional Camera and Light creation. /// </summary> /// <param name="paramsJson">JSON parameters containing sceneName, createCamera, createLight</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response with setup result</returns> public static string SetupScene(string paramsJson, object id) { #if UNITY_EDITOR try { var parameters = JsonUtility.FromJson<SceneSetupParams>(paramsJson); Scene scene; List<string> created = new List<string>(); // Get current active scene (don't create new scene to avoid GUI changes) scene = SceneManager.GetActiveScene(); // If sceneName is specified but doesn't match, return error if (!string.IsNullOrEmpty(parameters.sceneName) && scene.name != parameters.sceneName) { return JsonRpcResponseHelper.ErrorMessage( $"Scene name mismatch: Active scene is '{scene.name}', but requested '{parameters.sceneName}'. " + "Please open the desired scene first, or omit sceneName to use the active scene.", id); } // Create Camera if requested if (parameters.createCamera) { GameObject camera = new GameObject("Main Camera"); Camera cam = camera.AddComponent<Camera>(); camera.AddComponent<AudioListener>(); camera.tag = "MainCamera"; camera.transform.position = new Vector3(0, 1, -10); created.Add("Main Camera"); } // Create Light if requested if (parameters.createLight) { GameObject light = new GameObject("Directional Light"); Light lightComp = light.AddComponent<Light>(); lightComp.type = LightType.Directional; light.transform.rotation = Quaternion.Euler(50, -30, 0); created.Add("Directional Light"); } var result = new SceneSetupResult { status = "success", message = $"Scene '{scene.name}' setup completed", created = created.ToArray() }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetupScene error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to setup scene: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("SetupScene requires Unity Editor", id); #endif } /// <summary> /// Creates a new GameObject in the scene. /// </summary> /// <param name="paramsJson">JSON parameters containing name, parent, primitiveType, position</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response with creation result</returns> public static string CreateGameObject(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<CreateGameObjectParams>(paramsJson); GameObject go; // Create primitive or empty GameObject if (!string.IsNullOrEmpty(parameters.primitiveType)) { PrimitiveType primitiveTypeEnum; if (Enum.TryParse(parameters.primitiveType, true, out primitiveTypeEnum)) { go = GameObject.CreatePrimitive(primitiveTypeEnum); } else { return JsonRpcResponseHelper.InvalidParams( $"Invalid primitiveType: {parameters.primitiveType}. Valid types: Cube, Sphere, Capsule, Cylinder, Plane, Quad", id ); } } else { go = new GameObject(); } // Set name go.name = string.IsNullOrEmpty(parameters.name) ? "GameObject" : parameters.name; // Set position if (parameters.position != null) { go.transform.position = new Vector3( parameters.position.x, parameters.position.y, parameters.position.z ); } // Set parent if (!string.IsNullOrEmpty(parameters.parent)) { GameObject parentObj = GameObject.Find(parameters.parent); if (parentObj != null) { go.transform.SetParent(parentObj.transform); } else { Debug.LogWarning($"[MCP] Parent GameObject '{parameters.parent}' not found, creating at root"); } } // Build full path string path = GetGameObjectPath(go); var result = new CreateGameObjectResult { status = "success", path = path, instanceId = go.GetInstanceID() }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] CreateGameObject error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to create GameObject: {e.Message}", id); } } /// <summary> /// Deletes a GameObject from the scene. /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string DeleteGameObject(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<GameObjectPathParams>(paramsJson); if (string.IsNullOrEmpty(parameters.gameObjectPath)) { return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id); } GameObject go = GameObject.Find(parameters.gameObjectPath); if (go == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject '{parameters.gameObjectPath}' not found", id); } string name = go.name; UnityEngine.Object.DestroyImmediate(go); var result = new GameObjectOperationResult { success = true, message = $"GameObject '{name}' deleted successfully" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] DeleteGameObject error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to delete GameObject: {e.Message}", id); } } /// <summary> /// Sets the active state of a GameObject. /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath and active state</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SetGameObjectActive(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<SetGameObjectActiveParams>(paramsJson); if (string.IsNullOrEmpty(parameters.gameObjectPath)) { return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id); } GameObject go = GameObject.Find(parameters.gameObjectPath); if (go == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject '{parameters.gameObjectPath}' not found", id); } go.SetActive(parameters.active); var result = new GameObjectOperationResult { success = true, message = $"GameObject '{go.name}' active state set to {parameters.active}" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetGameObjectActive error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to set GameObject active state: {e.Message}", id); } } /// <summary> /// Sets the parent of a GameObject. /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath and parentPath</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SetGameObjectParent(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<SetGameObjectParentParams>(paramsJson); if (string.IsNullOrEmpty(parameters.gameObjectPath)) { return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id); } GameObject go = GameObject.Find(parameters.gameObjectPath); if (go == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject '{parameters.gameObjectPath}' not found", id); } Transform newParent = null; if (!string.IsNullOrEmpty(parameters.parentPath)) { GameObject parentGo = GameObject.Find(parameters.parentPath); if (parentGo == null) { return JsonRpcResponseHelper.ErrorMessage($"Parent GameObject '{parameters.parentPath}' not found", id); } newParent = parentGo.transform; } go.transform.SetParent(newParent, parameters.worldPositionStays); var result = new GameObjectOperationResult { success = true, message = $"GameObject '{go.name}' parent set successfully" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetGameObjectParent error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to set GameObject parent: {e.Message}", id); } } /// <summary> /// Sets the world position of a GameObject. /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath and position</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SetTransformPosition(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<SetTransformPositionParams>(paramsJson); if (string.IsNullOrEmpty(parameters.gameObjectPath)) { return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id); } GameObject go = GameObject.Find(parameters.gameObjectPath); if (go == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject '{parameters.gameObjectPath}' not found", id); } if (parameters.local) { go.transform.localPosition = new Vector3(parameters.x, parameters.y, parameters.z); } else { go.transform.position = new Vector3(parameters.x, parameters.y, parameters.z); } var result = new GameObjectOperationResult { success = true, message = $"GameObject '{go.name}' position set to ({parameters.x}, {parameters.y}, {parameters.z})" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetTransformPosition error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to set transform position: {e.Message}", id); } } /// <summary> /// Sets the rotation of a GameObject. /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath and rotation</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SetTransformRotation(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<SetTransformRotationParams>(paramsJson); if (string.IsNullOrEmpty(parameters.gameObjectPath)) { return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id); } GameObject go = GameObject.Find(parameters.gameObjectPath); if (go == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject '{parameters.gameObjectPath}' not found", id); } Quaternion rotation = Quaternion.Euler(parameters.x, parameters.y, parameters.z); if (parameters.local) { go.transform.localRotation = rotation; } else { go.transform.rotation = rotation; } var result = new GameObjectOperationResult { success = true, message = $"GameObject '{go.name}' rotation set to ({parameters.x}, {parameters.y}, {parameters.z})" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetTransformRotation error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to set transform rotation: {e.Message}", id); } } /// <summary> /// Sets the scale of a GameObject. /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath and scale</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SetTransformScale(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<SetTransformScaleParams>(paramsJson); if (string.IsNullOrEmpty(parameters.gameObjectPath)) { return JsonRpcResponseHelper.InvalidParams("gameObjectPath is required", id); } GameObject go = GameObject.Find(parameters.gameObjectPath); if (go == null) { return JsonRpcResponseHelper.ErrorMessage($"GameObject '{parameters.gameObjectPath}' not found", id); } go.transform.localScale = new Vector3(parameters.x, parameters.y, parameters.z); var result = new GameObjectOperationResult { success = true, message = $"GameObject '{go.name}' scale set to ({parameters.x}, {parameters.y}, {parameters.z})" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetTransformScale error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to set transform scale: {e.Message}", id); } } /// <summary> /// Destroys a GameObject from the scene. /// API: unity.gameobject.destroy /// </summary> /// <param name="paramsJson">JSON parameters containing gameObjectPath or instanceId</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string DestroyGameObject(string paramsJson, object id) { #if UNITY_EDITOR try { var parameters = JsonUtility.FromJson<DestroyGameObjectParams>(paramsJson); GameObject go = null; // Try to find by instanceId first if (parameters.instanceId != 0) { go = EditorUtility.InstanceIDToObject(parameters.instanceId) as GameObject; } // If not found by instanceId, try by path if (go == null && !string.IsNullOrEmpty(parameters.gameObjectPath)) { go = GameObject.Find(parameters.gameObjectPath); } if (go == null) { string identifier = parameters.instanceId != 0 ? $"instanceId:{parameters.instanceId}" : parameters.gameObjectPath; return JsonRpcResponseHelper.ErrorMessage($"GameObject not found: {identifier}", id); } string destroyedName = go.name; string destroyedPath = GetGameObjectPath(go); // Register undo before destroying Undo.DestroyObjectImmediate(go); var result = new DestroyGameObjectResult { success = true, message = $"GameObject '{destroyedName}' destroyed", destroyedPath = destroyedPath, destroyedName = destroyedName }; Debug.Log($"[MCP] GameObject destroyed: {destroyedPath}"); var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] DestroyGameObject error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to destroy GameObject: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("DestroyGameObject requires Unity Editor", id); #endif } /// <summary> /// Recursively serializes a GameObject and its children. /// </summary> /// <param name="go">GameObject to serialize</param> /// <param name="path">Hierarchical path</param> /// <param name="currentDepth">Current depth level</param> /// <param name="maxDepth">Maximum depth to serialize</param> private static GameObjectData SerializeGameObject(GameObject go, string path, int currentDepth, int maxDepth) { var data = new GameObjectData { name = go.name, path = path, active = go.activeInHierarchy, transform = new TransformData { position = new Vector3Data { x = go.transform.position.x, y = go.transform.position.y, z = go.transform.position.z }, rotation = new QuaternionData { x = go.transform.rotation.x, y = go.transform.rotation.y, z = go.transform.rotation.z, w = go.transform.rotation.w }, scale = new Vector3Data { x = go.transform.localScale.x, y = go.transform.localScale.y, z = go.transform.localScale.z } } }; // Get component names Component[] components = go.GetComponents<Component>(); List<string> componentNames = new List<string>(); foreach (Component comp in components) { if (comp != null) { componentNames.Add(comp.GetType().Name); } } data.components = componentNames.ToArray(); // Serialize children recursively only if within depth limit List<GameObjectData> children = new List<GameObjectData>(); if (currentDepth < maxDepth) { for (int i = 0; i < go.transform.childCount; i++) { GameObject child = go.transform.GetChild(i).gameObject; string childPath = path + "/" + child.name; children.Add(SerializeGameObject(child, childPath, currentDepth + 1, maxDepth)); } } data.children = children.ToArray(); data.childCount = go.transform.childCount; // Store total child count even if not serialized return data; } /// <summary> /// Gets the full hierarchical path of a GameObject. /// </summary> private static string GetGameObjectPath(GameObject go) { string path = go.name; Transform current = go.transform.parent; while (current != null) { path = current.name + "/" + path; current = current.parent; } return path; } /// <summary> /// Opens an existing scene file in Unity Editor. /// </summary> /// <param name="paramsJson">JSON parameters containing scenePath and optional mode</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string OpenScene(string paramsJson, object id) { #if UNITY_EDITOR try { var parameters = JsonUtility.FromJson<OpenSceneParams>(paramsJson); if (string.IsNullOrEmpty(parameters.scenePath)) { return JsonRpcResponseHelper.InvalidParams("scenePath is required", id); } // Check if scene file exists if (!File.Exists(parameters.scenePath)) { return JsonRpcResponseHelper.ErrorMessage($"Scene file not found: {parameters.scenePath}", id); } // Determine open mode OpenSceneMode mode = OpenSceneMode.Single; if (!string.IsNullOrEmpty(parameters.mode)) { switch (parameters.mode.ToLower()) { case "additive": mode = OpenSceneMode.Additive; break; case "single": default: mode = OpenSceneMode.Single; break; } } // Save current scene(s) if modified to avoid save dialog if (mode == OpenSceneMode.Single) { // Save all modified scenes before opening new scene for (int i = 0; i < SceneManager.sceneCount; i++) { Scene currentScene = SceneManager.GetSceneAt(i); if (currentScene.isDirty) { // If scene has never been saved (path is empty), skip saving if (!string.IsNullOrEmpty(currentScene.path)) { EditorSceneManager.SaveScene(currentScene); } } } } // Open the scene Scene openedScene = EditorSceneManager.OpenScene(parameters.scenePath, mode); if (!openedScene.IsValid()) { return JsonRpcResponseHelper.ErrorMessage($"Failed to open scene: {parameters.scenePath}", id); } var result = new OpenSceneResult { success = true, scenePath = openedScene.path, sceneName = openedScene.name, isLoaded = openedScene.isLoaded, mode = mode.ToString() }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] OpenScene error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to open scene: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("OpenScene requires Unity Editor", id); #endif } /// <summary> /// Saves the current scene or a specific scene by path. /// </summary> /// <param name="paramsJson">JSON parameters containing optional scenePath</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SaveScene(string paramsJson, object id) { #if UNITY_EDITOR try { var parameters = JsonUtility.FromJson<SaveSceneParams>(paramsJson); Scene scene; if (!string.IsNullOrEmpty(parameters.scenePath)) { scene = SceneManager.GetSceneByPath(parameters.scenePath); if (!scene.IsValid()) { return JsonRpcResponseHelper.ErrorMessage($"Scene '{parameters.scenePath}' not found", id); } } else { scene = SceneManager.GetActiveScene(); } bool success = EditorSceneManager.SaveScene(scene); if (!success) { return JsonRpcResponseHelper.ErrorMessage("Failed to save scene", id); } var result = new SaveSceneResult { success = true, scenePath = scene.path, sceneName = scene.name }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SaveScene error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to save scene: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("SaveScene requires Unity Editor", id); #endif } /// <summary> /// Saves all modified assets in the project to disk. /// Equivalent to File > Save Project (Ctrl+S on assets). /// This saves .controller, .overrideController, .asset files etc. /// </summary> /// <param name="paramsJson">JSON parameters (none required)</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SaveProject(string paramsJson, object id) { #if UNITY_EDITOR try { // Save all modified assets to disk AssetDatabase.SaveAssets(); // Refresh the AssetDatabase to ensure changes are recognized AssetDatabase.Refresh(); var result = new SaveProjectResult { success = true, message = "All modified assets have been saved to disk" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SaveProject error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to save project: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("SaveProject requires Unity Editor", id); #endif } /// <summary> /// Executes a Unity Editor menu item. /// </summary> /// <param name="paramsJson">JSON parameters containing menuPath</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string ExecuteMenuItem(string paramsJson, object id) { #if UNITY_EDITOR try { var parameters = JsonUtility.FromJson<ExecuteMenuItemParams>(paramsJson); if (string.IsNullOrEmpty(parameters.menuPath)) { return JsonRpcResponseHelper.InvalidParams("menuPath is required", id); } bool success = EditorApplication.ExecuteMenuItem(parameters.menuPath); if (!success) { return JsonRpcResponseHelper.ErrorMessage($"MenuItem '{parameters.menuPath}' not found or failed to execute", id); } var result = new ExecuteMenuItemResult { success = true, menuPath = parameters.menuPath }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] ExecuteMenuItem error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to execute MenuItem: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("ExecuteMenuItem requires Unity Editor", id); #endif } /// <summary> /// Finds an object of a specific type in the scene (equivalent to Object.FindObjectOfType). /// </summary> /// <param name="paramsJson">JSON parameters containing componentType and optional includeInactive</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response containing GameObject path and component info</returns> public static string FindObjectOfType(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<FindObjectOfTypeParams>(paramsJson); if (string.IsNullOrEmpty(parameters.componentType)) { return JsonRpcResponseHelper.InvalidParams("componentType is required", id); } // Find the component type across all loaded assemblies Type componentType = ComponentReflection.FindComponentType(parameters.componentType); if (componentType == null) { return JsonRpcResponseHelper.ErrorMessage($"Component type '{parameters.componentType}' not found", id); } // Use Resources.FindObjectsOfTypeAll for inactive objects, or Object.FindObjectOfType for active only UnityEngine.Object foundObject; if (parameters.includeInactive) { // FindObjectsOfTypeAll includes inactive objects var allObjects = Resources.FindObjectsOfTypeAll(componentType); // Filter out objects that are part of assets (not in scene) foundObject = null; foreach (var obj in allObjects) { if (obj is Component comp && comp.gameObject.scene.IsValid()) { foundObject = obj; break; } } } else { // Find only active objects in scene foundObject = UnityEngine.Object.FindObjectOfType(componentType); } if (foundObject == null) { var result = new FindObjectOfTypeResult { found = false, componentType = parameters.componentType }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } // Get GameObject and path GameObject go = null; if (foundObject is Component comp2) { go = comp2.gameObject; } else if (foundObject is GameObject gameObj) { go = gameObj; } if (go == null) { return JsonRpcResponseHelper.ErrorMessage("Found object is not a Component or GameObject", id); } string path = GetGameObjectPath(go); var successResult = new FindObjectOfTypeResult { found = true, gameObjectPath = path, gameObjectName = go.name, componentType = componentType.FullName, instanceId = go.GetInstanceID(), isActive = go.activeInHierarchy }; var successResponse = JsonRpcResponse.Success(successResult, id); return successResponse.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] FindObjectOfType error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to find object of type: {e.Message}", id); } } /// <summary> /// Outputs a debug log message to Unity Console. /// </summary> /// <param name="paramsJson">JSON parameters containing message and logType</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string DebugLog(string paramsJson, object id) { try { var parameters = JsonUtility.FromJson<DebugLogParams>(paramsJson); if (string.IsNullOrEmpty(parameters.message)) { return JsonRpcResponseHelper.InvalidParams("message is required", id); } // Output log based on type switch (parameters.logType?.ToLower()) { case "warning": Debug.LogWarning($"[MCP Client] {parameters.message}"); break; case "error": Debug.LogError($"[MCP Client] {parameters.message}"); break; default: Debug.Log($"[MCP Client] {parameters.message}"); break; } var result = new DebugLogResult { success = true, message = parameters.message, logType = parameters.logType ?? "log" }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] DebugLog error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to output debug log: {e.Message}", id); } } /// <summary> /// Sets up complete scene architecture for Avatar system. /// Creates GameObjects, components, ScriptableObjects, and configures all references. /// </summary> /// <param name="paramsJson">JSON parameters containing optional configuration</param> /// <param name="id">JSON-RPC request ID</param> /// <returns>JSON-RPC response</returns> public static string SetupSceneArchitecture(string paramsJson, object id) { #if UNITY_EDITOR try { var parameters = JsonUtility.FromJson<SetupSceneArchitectureParams>(paramsJson); List<string> steps = new List<string>(); // 1. Create AvatarPlacementSettings ScriptableObject steps.Add("Creating AvatarPlacementSettings ScriptableObject..."); string settingsPath = "Assets/Settings/DefaultAvatarPlacementSettings.asset"; var settingsResult = UnityAssetCreator.CreateScriptableObject( JsonUtility.ToJson(new UnityAssetCreator.CreateScriptableObjectParams { path = settingsPath, typeName = "AICam.VRM.AvatarPlacementSettings" }), null ); // 2. Create GameObjects steps.Add("Creating AvatarInstanceManager GameObject..."); CreateGameObject(JsonUtility.ToJson(new CreateGameObjectParams { name = "AvatarInstanceManager" }), null); steps.Add("Creating AvatarFollowController GameObject..."); CreateGameObject(JsonUtility.ToJson(new CreateGameObjectParams { name = "AvatarFollowController" }), null); steps.Add("Creating AvatarSwipeController GameObject..."); CreateGameObject(JsonUtility.ToJson(new CreateGameObjectParams { name = "AvatarSwipeController" }), null); // 3. Add Components steps.Add("Adding AvatarInstanceManager component..."); ComponentReflection.AddComponent( JsonUtility.ToJson(new ComponentReflection.ComponentAddParams { gameObjectPath = "AvatarInstanceManager", componentType = "AICam.VRM.AvatarInstanceManager" }), null ); steps.Add("Adding AvatarFollowController component..."); ComponentReflection.AddComponent( JsonUtility.ToJson(new ComponentReflection.ComponentAddParams { gameObjectPath = "AvatarFollowController", componentType = "AICam.VRM.AvatarFollowController" }), null ); steps.Add("Adding AvatarSwipeController component..."); ComponentReflection.AddComponent( JsonUtility.ToJson(new ComponentReflection.ComponentAddParams { gameObjectPath = "AvatarSwipeController", componentType = "AICam.VRM.AvatarSwipeController" }), null ); // 4. Setup AvatarInstanceManager references steps.Add("Setting up AvatarInstanceManager references..."); // Set placementSettings ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = "AvatarInstanceManager", componentType = "AICam.VRM.AvatarInstanceManager", fieldName = "placementSettings", referenceType = "asset", referencePath = settingsPath }), null ); // Find and set AR Camera var arCameraPath = "XR Origin/Camera Offset/AR Camera"; // Standard AR Foundation path ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = "AvatarInstanceManager", componentType = "AICam.VRM.AvatarInstanceManager", fieldName = "arCamera", referenceType = "component", referenceGameObjectPath = arCameraPath, referenceComponentType = "UnityEngine.Camera" }), null ); // Find ARAnchorManager using FindObjectOfType steps.Add("Finding ARAnchorManager in scene..."); string findResult = FindObjectOfType( JsonUtility.ToJson(new FindObjectOfTypeParams { componentType = "UnityEngine.XR.ARFoundation.ARAnchorManager", includeInactive = false }), null ); var findResponse = JsonUtility.FromJson<JsonRpcResponse>(findResult); if (findResponse.result != null) { var findData = JsonUtility.FromJson<FindObjectOfTypeResult>(JsonUtility.ToJson(findResponse.result)); if (findData.found) { steps.Add($"Found ARAnchorManager at: {findData.gameObjectPath}"); ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = "AvatarInstanceManager", componentType = "AICam.VRM.AvatarInstanceManager", fieldName = "anchorManager", referenceType = "component", referenceGameObjectPath = findData.gameObjectPath, referenceComponentType = "UnityEngine.XR.ARFoundation.ARAnchorManager" }), null ); } else { steps.Add("Warning: ARAnchorManager not found in scene"); } } // 5. Setup AvatarFollowController steps.Add("Setting up AvatarFollowController..."); // Set arCamera reference ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = "AvatarFollowController", componentType = "AICam.VRM.AvatarFollowController", fieldName = "arCamera", referenceType = "component", referenceGameObjectPath = arCameraPath, referenceComponentType = "UnityEngine.Camera" }), null ); // Set initial parameters ComponentReflection.SetField( JsonUtility.ToJson(new ComponentReflection.ComponentSetParams { gameObjectPath = "AvatarFollowController", componentType = "AICam.VRM.AvatarFollowController", fieldName = "followDistance", value = 1.5f }), null ); // 6. Setup AvatarSwipeController steps.Add("Setting up AvatarSwipeController..."); // Set followController reference ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = "AvatarSwipeController", componentType = "AICam.VRM.AvatarSwipeController", fieldName = "followController", referenceType = "component", referenceGameObjectPath = "AvatarFollowController", referenceComponentType = "AICam.VRM.AvatarFollowController" }), null ); // 7. Find and setup PlaceAvatarOnPlaneOnly if exists steps.Add("Looking for PlaceAvatarOnPlaneOnly component..."); string placeAvatarFind = FindObjectOfType( JsonUtility.ToJson(new FindObjectOfTypeParams { componentType = "AICam.VRM.PlaceAvatarOnPlaneOnly", includeInactive = false }), null ); var placeAvatarResponse = JsonUtility.FromJson<JsonRpcResponse>(placeAvatarFind); if (placeAvatarResponse.result != null) { var placeAvatarData = JsonUtility.FromJson<FindObjectOfTypeResult>(JsonUtility.ToJson(placeAvatarResponse.result)); if (placeAvatarData.found) { steps.Add($"Found PlaceAvatarOnPlaneOnly at: {placeAvatarData.gameObjectPath}"); // Set avatarInstanceManager ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = placeAvatarData.gameObjectPath, componentType = "AICam.VRM.PlaceAvatarOnPlaneOnly", fieldName = "avatarInstanceManager", referenceType = "component", referenceGameObjectPath = "AvatarInstanceManager", referenceComponentType = "AICam.VRM.AvatarInstanceManager" }), null ); // Set avatarFollowController ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = placeAvatarData.gameObjectPath, componentType = "AICam.VRM.PlaceAvatarOnPlaneOnly", fieldName = "avatarFollowController", referenceType = "component", referenceGameObjectPath = "AvatarFollowController", referenceComponentType = "AICam.VRM.AvatarFollowController" }), null ); // Set avatarSwipeController ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = placeAvatarData.gameObjectPath, componentType = "AICam.VRM.PlaceAvatarOnPlaneOnly", fieldName = "avatarSwipeController", referenceType = "component", referenceGameObjectPath = "AvatarSwipeController", referenceComponentType = "AICam.VRM.AvatarSwipeController" }), null ); } } // 8. Find and setup CameraCaptureController if exists steps.Add("Looking for CameraCaptureController component..."); string cameraCaptureFind = FindObjectOfType( JsonUtility.ToJson(new FindObjectOfTypeParams { componentType = "AICam.VRM.CameraCaptureController", includeInactive = false }), null ); var cameraCaptureResponse = JsonUtility.FromJson<JsonRpcResponse>(cameraCaptureFind); if (cameraCaptureResponse.result != null) { var cameraCaptureData = JsonUtility.FromJson<FindObjectOfTypeResult>(JsonUtility.ToJson(cameraCaptureResponse.result)); if (cameraCaptureData.found) { steps.Add($"Found CameraCaptureController at: {cameraCaptureData.gameObjectPath}"); // Set avatarInstanceManager ComponentReflection.SetReference( JsonUtility.ToJson(new ComponentReflection.ComponentSetReferenceParams { gameObjectPath = cameraCaptureData.gameObjectPath, componentType = "AICam.VRM.CameraCaptureController", fieldName = "avatarInstanceManager", referenceType = "component", referenceGameObjectPath = "AvatarInstanceManager", referenceComponentType = "AICam.VRM.AvatarInstanceManager" }), null ); } } // 9. Mark scene as dirty steps.Add("Marking scene as dirty..."); Scene scene = SceneManager.GetActiveScene(); UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene); steps.Add("Scene architecture setup complete!"); var result = new SetupSceneArchitectureResult { success = true, steps = steps.ToArray(), sceneName = scene.name }; var response = JsonRpcResponse.Success(result, id); return response.ToJson(); } catch (Exception e) { Debug.LogError($"[MCP] SetupSceneArchitecture error: {e.Message}\n{e.StackTrace}"); return JsonRpcResponseHelper.ErrorMessage($"Failed to setup scene architecture: {e.Message}", id); } #else return JsonRpcResponseHelper.ErrorMessage("SetupSceneArchitecture requires Unity Editor", id); #endif } } #region Data Structures /// <summary> /// Parameters for scene setup. /// </summary> [Serializable] public class SceneSetupParams { public string sceneName; public bool createCamera = true; public bool createLight = true; } /// <summary> /// Parameters for GameObject creation. /// </summary> [Serializable] public class CreateGameObjectParams { public string name; public string parent; public string primitiveType; public Vector3Data position; } /// <summary> /// Parameters for listing scene. /// </summary> [Serializable] public class SceneListParams { public int maxDepth = 2; // Maximum depth to serialize (default: 2) } /// <summary> /// Result of scene listing. /// </summary> [Serializable] public class SceneListResult { public string sceneName; public GameObjectData[] rootObjects; public int maxDepth; // The depth limit used } /// <summary> /// Result of scene setup. /// </summary> [Serializable] public class SceneSetupResult { public string status; public string message; public string[] created; } /// <summary> /// Result of GameObject creation. /// </summary> [Serializable] public class CreateGameObjectResult { public string status; public string path; public int instanceId; } /// <summary> /// Serializable GameObject data. /// </summary> [Serializable] public class GameObjectData { public string name; public string path; public bool active; public TransformData transform; public string[] components; public GameObjectData[] children; public int childCount; // Total number of children (even if not all serialized) } /// <summary> /// Serializable Transform data. /// </summary> [Serializable] public class TransformData { public Vector3Data position; public QuaternionData rotation; public Vector3Data scale; } /// <summary> /// Serializable Vector2 data. /// </summary> [Serializable] public class Vector2Data { public float x; public float y; } /// <summary> /// Serializable Vector3 data. /// </summary> [Serializable] public class Vector3Data { public float x; public float y; public float z; } /// <summary> /// Serializable Vector4 data. /// </summary> [Serializable] public class Vector4Data { public float x; public float y; public float z; public float w; } /// <summary> /// Serializable Quaternion data. /// </summary> [Serializable] public class QuaternionData { public float x; public float y; public float z; public float w; } /// <summary> /// Parameters for debug log output. /// </summary> [Serializable] public class DebugLogParams { public string message; public string logType; // "log", "warning", "error" } /// <summary> /// Result of debug log output. /// </summary> [Serializable] public class DebugLogResult { public bool success; public string message; public string logType; } /// <summary> /// Parameters for opening a scene. /// </summary> [Serializable] public class OpenSceneParams { public string scenePath; // Required: path to the scene file public string mode; // Optional: "single" (default) or "additive" } /// <summary> /// Result of opening a scene. /// </summary> [Serializable] public class OpenSceneResult { public bool success; public string scenePath; public string sceneName; public bool isLoaded; public string mode; } /// <summary> /// Parameters for saving a scene. /// </summary> [Serializable] public class SaveSceneParams { public string scenePath; // Optional, null = save active scene } /// <summary> /// Result of saving a scene. /// </summary> [Serializable] public class SaveSceneResult { public bool success; public string scenePath; public string sceneName; } /// <summary> /// Result of saving the project (all modified assets). /// </summary> [Serializable] public class SaveProjectResult { public bool success; public string message; } /// <summary> /// Parameters for executing a menu item. /// </summary> [Serializable] public class ExecuteMenuItemParams { public string menuPath; } /// <summary> /// Result of executing a menu item. /// </summary> [Serializable] public class ExecuteMenuItemResult { public bool success; public string menuPath; } /// <summary> /// Parameters for finding an object of a specific type. /// </summary> [Serializable] public class FindObjectOfTypeParams { public string componentType; public bool includeInactive = false; // Optional, default: false (active only) } /// <summary> /// Result of finding an object of a specific type. /// </summary> [Serializable] public class FindObjectOfTypeResult { public bool found; public string gameObjectPath; public string gameObjectName; public string componentType; public int instanceId; public bool isActive; } /// <summary> /// Parameters for scene architecture setup. /// </summary> [Serializable] public class SetupSceneArchitectureParams { // Currently no parameters needed, but structure for future expansion } /// <summary> /// Result of scene architecture setup. /// </summary> [Serializable] public class SetupSceneArchitectureResult { public bool success; public string[] steps; public string sceneName; } /// <summary> /// Parameters for GameObject path reference. /// </summary> [Serializable] public class GameObjectPathParams { public string gameObjectPath; } /// <summary> /// Parameters for setting GameObject active state. /// </summary> [Serializable] public class SetGameObjectActiveParams { public string gameObjectPath; public bool active; } /// <summary> /// Parameters for setting GameObject parent. /// </summary> [Serializable] public class SetGameObjectParentParams { public string gameObjectPath; public string parentPath; // null or empty to set no parent (root) public bool worldPositionStays = true; } /// <summary> /// Parameters for setting transform position. /// </summary> [Serializable] public class SetTransformPositionParams { public string gameObjectPath; public float x; public float y; public float z; public bool local = false; } /// <summary> /// Parameters for setting transform rotation (Euler angles). /// </summary> [Serializable] public class SetTransformRotationParams { public string gameObjectPath; public float x; public float y; public float z; public bool local = false; } /// <summary> /// Parameters for setting transform scale. /// </summary> [Serializable] public class SetTransformScaleParams { public string gameObjectPath; public float x; public float y; public float z; } /// <summary> /// Result of GameObject operation. /// </summary> [Serializable] public class GameObjectOperationResult { public bool success; public string message; } /// <summary> /// Parameters for destroying a GameObject. /// </summary> [Serializable] public class DestroyGameObjectParams { public string gameObjectPath; // Path to find the GameObject public int instanceId; // Alternative: instanceId } /// <summary> /// Result of destroying a GameObject. /// </summary> [Serializable] public class DestroyGameObjectResult { public bool success; public string message; public string destroyedPath; public string destroyedName; } #endregion }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dsgarage/UniMCP4CC'

If you have feedback or need assistance with the MCP directory API, please join our Discord server